diff --git a/docs/proposals/feature-flags/feature-flags-suggestion.md b/docs/proposals/feature-flags/feature-flags-suggestion.md
new file mode 100644
index 0000000..67fed3b
--- /dev/null
+++ b/docs/proposals/feature-flags/feature-flags-suggestion.md
@@ -0,0 +1,300 @@
+# Feature Flags via a Published Config Endpoint — Proposal
+
+**Status:** Draft for review. Not yet implemented.
+**Origin:** fluent-ai is not hosted in any deployed environment. Every PR that
+depends on it therefore cannot merge — merging would light up UI (and code
+paths) that call a service that isn't there, which would block promoting the
+branch to production, which in turn blocks unrelated changes queued behind it.
+**Ships as:** a coordinated pair of PRs — fluent-api (the flag source + endpoint)
+and fluent-web (the consumer hook, a gate primitive, and an unlinked diagnostics
+page). A small fluent-platform compose/env note may follow.
+
+---
+
+## 1. Problem
+
+- fluent-ai has no hosted URL in `development`/`staging`/`production`. In those
+ environments `FLUENT_AI_URL` / `FLUENT_AI_KEY` point at nothing usable.
+- The AI-dependent feature that exists today is the **Repeated Word Check**
+ (fluent-web Checks tab/panel → `POST /ai/tools/greek-room/repeated-words`).
+- We need a way to **turn AI-dependent front-end features on and off per
+ environment** so their PRs can merge and ride to production **hidden**, and be
+ switched on later (in the environment where fluent-ai actually runs) with a
+ config change and no redeploy of code.
+
+This is fundamentally a **wiring / deploy concern**, not application data.
+
+---
+
+## 2. Goals & non-goals
+
+**Goals**
+
+- A single source of truth for "which optional features are on," owned by
+ fluent-api and driven by environment configuration.
+- A read-only endpoint fluent-web can query to decide what UI to render.
+- fluent-web hides AI UI by default and reveals it only when the flag says so.
+- An unlinked, login-gated diagnostics page where an operator can see the flag
+ state (and, over time, other technical/health details).
+- An **extensible** shape so the next feature/flag is added without changing the
+ contract or the consumer primitives.
+
+**Non-goals (this proposal)**
+
+- **No server-side enforcement.** This is a _publishing_ mechanism, deliberately
+ decoupled from the request path. Turning a flag off does **not** make
+ `/ai/tools/...` start rejecting requests. If AI is unconfigured the AI call
+ errors out on its own (a separate, pre-existing consequence — see §7). Coupling
+ publish-state to enforcement is explicitly rejected (**D5**).
+- **No database.** Flags are env-derived, not stored/edited at runtime (**D1**).
+- **No runtime toggling UI.** The diagnostics page is read-only (**D8**).
+
+---
+
+## 3. Design overview
+
+```mermaid
+flowchart TD
+ env["env (source of truth)
EN_FEATURE_* vars + AI wiring"]
+
+ subgraph api["fluent-api"]
+ build["buildFeatures()
reads env, derives flag map"]
+ end
+
+ subgraph web["fluent-web"]
+ hook["useFeatureFlags()"]
+ gate["<FeatureGate feature=...>
Checks tab renders only when on;
hidden by default / on error"]
+ diag["/_authenticated/debug
diagnostics page (read-only)"]
+ end
+
+ env --> build
+ build -->|"GET /config/features (unauthenticated)"| hook
+ hook --> gate
+ hook --> diag
+```
+
+- fluent-api **owns the truth** (from env) and publishes a **read-only
+ projection** of it. fluent-web never decides policy; it only reflects what the
+ API reports. (This resolves the apparent "env vs API" split: the API is not a
+ second source of truth, it is the _publication channel_ for the env truth.)
+
+---
+
+## 4. fluent-api: the flag source
+
+### 4.1 Env variables — one flat var per flag, `EN_FEATURE_*` prefix — **D1, D2**
+
+Each feature flag is its **own flat boolean env var** under a **dedicated
+`EN_FEATURE_` prefix**. The prefix is hopefully specific enough not to collide with unrelated vars. Each flag
+var is **declared explicitly in the Zod `EnvSchema`** in
+[`src/env.ts`](../../../src/env.ts):
+
+```ts
+const EnvSchema = z.object({
+ // ...existing vars...
+
+ // ── Feature flags (EN_FEATURE_*) ──────────────────────────────────────
+ // One flat boolean per optional feature. Each is declared explicitly (see
+ // D2) so the schema doubles as the authoritative catalog of known flags and
+ // validates values. All EN_FEATURE_* vars are swept into the published map
+ // (§4.3); the key after the prefix is camelCased for the wire.
+ //
+ // Repeated Word Check (the one AI-dependent feature today). Left optional so
+ // its default can be derived from AI wiring (§4.2).
+ //
+ // NB: use z.stringbool() (Zod ≥ 3.25), NOT z.coerce.boolean(). Coercion
+ // follows JS truthiness, so the string "false" would parse to `true` and
+ // silently INVERT the safe default — exactly the wrong failure mode for a
+ // flag whose job is to keep AI UI hidden. z.stringbool() parses the usual
+ // env spellings ("true"/"false", "1"/"0", "yes"/"no", "on"/"off",
+ // case-insensitive) and rejects anything else; optional() preserves the
+ // unset case so §4.2's derived default still applies.
+ EN_FEATURE_REPEATED_WORD_CHECK: z.stringbool().optional(),
+});
+```
+
+**Why declare each flag in the schema (a deliberate DRY violation).** We could
+sweep `process.env` for the prefix generically, but a fixed `z.object` **strips
+unknown keys** on parse — so an undeclared `EN_FEATURE_*` var would be dropped
+before we could read it. Rather than parse outside the schema, we **declare each
+flag** so: (a) the schema is the single authoritative list of known flags —
+useful documentation in its own right, alongside the existing
+[`.env.example`](../../../.env.example) (which already carries a "# Fluent-AI
+integration" section we extend); (b) values are validated/coerced like every
+other env var; (c) there are no surprises from stray env keys. Adding a flag is:
+one schema line + one `.env.example` line + one map entry (§4.3).
+
+### 4.2 Per-flag default — AI-wiring safety for the repeated-word check — **D2**
+
+The repeated-word-check flag is an explicit switch that **defaults to off when
+AI is not wired**:
+
+- If `EN_FEATURE_REPEATED_WORD_CHECK` is set explicitly → use it.
+- If unset → derive: `true` only when **both** `FLUENT_AI_URL` and
+ `FLUENT_AI_KEY` are present and non-empty; otherwise `false`.
+
+This is belt-and-suspenders: an operator can force it, but forgetting to set it
+in an environment where fluent-ai isn't wired yields the safe answer (off). It
+does **not** enforce anything — it only decides what we _publish_ (**D5**).
+Future non-AI flags need not carry this derivation; they can simply default to
+`false` when unset.
+
+### 4.3 Endpoint — **D3, D4**
+
+Add a **meta route** as a sibling of [`/health`](../../../src/routes/health.route.ts)
+in `src/routes/`:
+
+```http
+GET /config/features (unauthenticated — see Q1)
+
+200 OK
+Content-Type: application/json
+
+{
+ "features": {
+ "repeatedWordCheck": true
+ }
+}
+```
+
+- Placed in `src/routes/config.route.ts`, registered on `server` like
+ `health.route.ts`. `/config` namespaces future non-feature config cleanly.
+- Returns a **named map** (`features: { : boolean }`), never a bare boolean,
+ so new flags are additive (**D6**). The map is assembled from the declared
+ `EN_FEATURE_*` vars: the part after the prefix is **camelCased** for the wire
+ (`EN_FEATURE_REPEATED_WORD_CHECK` → `repeatedWordCheck`). Today the only key is
+ `repeatedWordCheck`, whose value is the effective flag from §4.2.
+- Response schema via `@hono/zod-openapi` so it appears in the OpenAPI doc like
+ every other route.
+
+**Alternative floated (not chosen): fold into `/health`.** "What services are
+enabled" is arguably a health/readiness concern, and `/health` is already
+unauthenticated and env-derived. We could instead extend the health payload with
+a `features` block and skip a new route. Downside: it overloads a liveness probe
+with product-config semantics and couples two audiences (uptime monitors vs. the
+SPA). **Recommendation: dedicated `/config/features`; reviewer may prefer the
+`/health` merge (Q2).**
+
+---
+
+## 5. fluent-web: consuming the flags
+
+### 5.1 A small, extensible primitive — **D6, D7**
+
+Add a feature-flags module (e.g. `src/features/flags/`) with:
+
+- **`useFeatureFlags()`** — a TanStack Query hook that GETs `/config/features`
+ and returns the typed `features` map. Cached like other config; a sensible
+ `staleTime` (flags change only on deploy/config).
+- **`useFeatureFlag(name)`** — thin selector returning a single boolean.
+- **`…`** — renders
+ children only when the named flag is on.
+
+The map shape (`Record`) means adding a feature later is a
+new key + a new gate usage — **no change to the hook or the component** (**D6**).
+
+### 5.2 Fail closed (hidden) — **D7**
+
+While flags are **loading**, or if the endpoint **errors/unreachable**, gated AI
+UI is **treated as off (hidden)**. This is the safe default given the entire
+point is "don't surface features whose backend isn't there." Non-AI/base UI is
+never gated and renders normally regardless.
+
+### 5.3 Wiring the one feature today
+
+The Checks tab/panel in the drafting area (repeated-word check) is wrapped so it
+appears only when `repeatedWordCheck` is on. Relevant surfaces:
+
+- Checks panel — `fluent-web/src/features/checks/components/ChecksPanel.tsx`
+- Drafting page host — `fluent-web/src/features/bible/components/DraftingPage.tsx`
+
+Exact insertion point is an implementation detail for the fluent-web PR; the
+contract here is only "gate the AI UI on `repeatedWordCheck`, default hidden."
+
+---
+
+## 6. fluent-web: the diagnostics page — **D8**
+
+An **unlinked** page under the existing `_authenticated` layout (login required,
+**no** role check), e.g. route `/_authenticated/debug` (final path TBD in the
+web PR). Precedent: the router already has a login gate via `_authenticated` and
+a Manager-only gate on `/users` — we intentionally use only the login gate here.
+
+- **Unlinked by design**: no nav entry. "If you know about it, you know about
+ it." The URL is _obscurity, not a security boundary_ — note the underlying
+ endpoint is unauthenticated (Q1), so nothing sensitive should ever live here.
+- **Read-only**: displays the flag map as the API reports it. No toggles.
+- **Framing / future use**: a `chrome://`-style catch-all for technical details
+ — whoever keeps things running peeks here to see what's on/off and, over time,
+ other health/technical info (build/version, `/health` echo, env name, etc.).
+ So it is scoped as "diagnostics page that currently shows feature flags,"
+ not "the feature-flags page."
+
+---
+
+## 7. Interaction with the actual AI call path (out of scope, noted)
+
+Because there is **no enforcement** (**D5**), a client that hits
+`/ai/tools/greek-room/repeated-words` while the flag is off still reaches the
+existing proxy, which will fail on its own if fluent-ai is unreachable (the
+client already maps this to `AI_SERVICE_UNAVAILABLE` → 502; see
+[`fluent-ai.client.ts`](../../../src/lib/services/fluent-ai/fluent-ai.client.ts)).
+That is a **pre-existing, separate consequence** of AI not being wired — this
+proposal neither introduces nor removes it. The flag's job is purely to keep the
+UI (and thus normal users) away from that path in environments where it can't
+work.
+
+---
+
+## 8. Decisions
+
+| ID | Decision | Rationale |
+| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| D1 | Flags are **env-sourced, not stored in the DB**. | This is a deploy/wiring concern. A DB adds a migration + admin surface for something that changes only when infra changes. |
+| D2 | **One flat boolean env var per flag under the `EN_FEATURE_*` prefix**, each **declared explicitly in the Zod schema**. The repeated-word-check flag defaults OFF when `FLUENT_AI_URL`/`FLUENT_AI_KEY` are absent. | Specific prefix avoids collisions; declaring each flag makes the schema the authoritative catalog and survives Zod's unknown-key stripping. Operator can force it; forgetting it where AI isn't wired yields the safe answer. |
+| D3 | Expose via a **dedicated meta route `GET /config/features`** in `src/routes/`, sibling of `/health`. | Matches the `/health` precedent (unauthenticated, env-derived, DB-less). `/config` leaves room to grow. |
+| D4 | Response is a **named map** `{ features: { : boolean } }` assembled from the `EN_FEATURE_*` vars (prefix stripped, camelCased). | Additive: new flags need no contract change; env keys never leak verbatim onto the wire. |
+| D5 | **No server-side enforcement.** Publishing is decoupled from the AI request path. | Keeps the flag a pure signal; enforcement/behavior on missing AI is a separate, pre-existing concern. |
+| D6 | fluent-web consumes flags through a **reusable `useFeatureFlags` hook + `FeatureGate` primitive** over a `Record`. | Adding a feature later is a key + a gate usage, nothing more. |
+| D7 | AI/gated UI **fails closed (hidden)** while loading or on endpoint error. | The whole point is to not surface features whose backend isn't there. |
+| D8 | A **login-gated, unlinked, read-only diagnostics page** hosts the flag view (extensible to other technical/health info). | Reuses the existing `_authenticated` gate; "know the URL" access; grows into a `chrome://`-style ops page. |
+
+---
+
+## 9. Open questions for the reviewer
+
+1. **Endpoint auth (Q1).** This proposal makes `GET /config/features`
+ **unauthenticated** (like `/health`), on the grounds that it reveals only
+ "is an optional feature on" — no data, no secrets — and lets the SPA decide
+ what to render independent of login. **Is unauthenticated acceptable here, or
+ should it require a session?** (If it must be authed, the diagnostics page's
+ login gate becomes a real boundary rather than mere obscurity.)
+2. **Route vs. `/health` (Q2).** Dedicated `GET /config/features` (recommended)
+ vs. folding a `features` block into the existing `/health` payload, since
+ "what's enabled" is arguably a health concern. Preference?
+3. **Prefix & map key (Q3).** Is `EN_FEATURE_*` the right prefix (specific
+ enough to avoid collisions), and `EN_FEATURE_REPEATED_WORD_CHECK` →
+ `repeatedWordCheck` the right var/key naming (vs. `greekRoomRepeatedWords`,
+ `aiRepeatedWords`, etc.)? Also: are we comfortable declaring each flag var
+ explicitly in the schema (a small DRY cost that buys documentation +
+ validation), vs. a generic prefix sweep?
+4. **Default-derivation (Q4).** Deriving the unset default of
+ `EN_FEATURE_REPEATED_WORD_CHECK` from `FLUENT_AI_URL`/`FLUENT_AI_KEY`
+ presence — desirable safety, or too clever (prefer a plain default of `false`
+ and require operators to set it)?
+
+---
+
+## 10. Rollout
+
+1. **fluent-api PR** — add `EN_FEATURE_REPEATED_WORD_CHECK` to the env schema and
+ to [`.env.example`](../../../.env.example), add the `buildFeatures()` deriver
+ (sweep declared `EN_FEATURE_*` → camelCased map, with the AI-wiring default),
+ the `GET /config/features` route + OpenAPI schema + tests. Merges safely:
+ default is off wherever AI isn't wired.
+2. **fluent-web PR** — add `useFeatureFlags`/`FeatureGate`, gate the Checks UI
+ (default hidden), add the unlinked diagnostics page. Merges safely: with the
+ flag off (prod), the Checks UI simply doesn't render — unblocking the AI PRs.
+3. **Later, per environment** — set `EN_FEATURE_REPEATED_WORD_CHECK=true` (and
+ wire `FLUENT_AI_URL`/`FLUENT_AI_KEY`) wherever fluent-ai is actually hosted.
+ No code redeploy required to flip it.
diff --git a/docs/proposals/feature-flags/feature-flags-summary.md b/docs/proposals/feature-flags/feature-flags-summary.md
new file mode 100644
index 0000000..abbbe63
--- /dev/null
+++ b/docs/proposals/feature-flags/feature-flags-summary.md
@@ -0,0 +1,74 @@
+# Feature Flags — Review Summary
+
+**Status:** Draft for review. Not yet implemented.
+
+**Purpose:** Reviewer orientation for a lightweight feature-flag mechanism. The
+full design is in the sibling [`feature-flags-suggestion.md`](feature-flags-suggestion.md)
+(problem, design, decisions **D1–D8**, open questions **Q1–Q4**). Ships as a
+coordinated pair of PRs — fluent-api (flag source + endpoint) and fluent-web
+(consumer hook, gate primitive, diagnostics page).
+
+## The problem
+
+fluent-ai is **not hosted** in any deployed environment. Every PR that depends on
+it therefore can't merge — doing so would surface UI whose backend isn't there
+and block promoting to production, which stalls unrelated work queued behind it.
+We need to **turn AI-dependent front-end features on/off per environment** so
+their PRs can merge and ride to production **hidden**, then be switched on later
+(where fluent-ai actually runs) with a config change and no code redeploy. This
+is a **wiring/deploy concern**, not application data.
+
+## What's being proposed
+
+1. **Env-sourced flags, one flat var per flag** under a specific `EN_FEATURE_*`
+ prefix (e.g. `EN_FEATURE_REPEATED_WORD_CHECK`), each **declared explicitly in
+ the Zod env schema** — the schema doubles as the authoritative flag catalog
+ and survives Zod's unknown-key stripping. No database (**D1, D2**).
+2. **A read-only endpoint `GET /config/features`** — a new unauthenticated meta
+ route (sibling of `/health`) returning a **named map**,
+ `{ features: { repeatedWordCheck: true } }`, assembled from the `EN_FEATURE_*`
+ vars (prefix stripped, camelCased). New flags are purely additive (**D3, D4**).
+3. **fluent-api owns the truth, publishes a projection.** The env is the single
+ source of truth; the endpoint is the publication channel, not a second
+ source. fluent-web never decides policy — it only reflects.
+4. **Safe default.** The repeated-word-check flag defaults **off** unless
+ `FLUENT_AI_URL` + `FLUENT_AI_KEY` are both wired, so forgetting to set it in
+ an AI-less environment yields the safe answer (**D2**).
+5. **fluent-web consumes it** via a small extensible primitive — a
+ `useFeatureFlags()` TanStack Query hook + a `` wrapper over a
+ `Record`. Gated AI UI **fails closed (hidden)** while loading
+ or on endpoint error (**D6, D7**).
+6. **An unlinked, login-gated, read-only diagnostics page** (a `chrome://`-style
+ catch-all for technical/health details, starting with the flag map). Reuses
+ the existing `_authenticated` gate; no role check (**D8**).
+
+## Explicitly out of scope
+
+- **No server-side enforcement.** Turning a flag off does **not** gate the AI
+ endpoints — publishing is deliberately decoupled from the request path. If AI
+ is unconfigured, the AI call fails on its own (a pre-existing, separate
+ consequence). (**D5**)
+- No database, no runtime toggling UI, no per-user/tenant targeting, no
+ percentage rollouts. This is a per-environment on/off wiring switch.
+
+## Areas where input would be most valuable
+
+1. **Endpoint auth (Q1).** `GET /config/features` is proposed **unauthenticated**
+ (like `/health`) — it reveals only "is a feature on," no data/secrets. OK, or
+ require a session? (If authed, the diagnostics page's login gate becomes a
+ real boundary rather than obscurity.)
+2. **Route vs. `/health` (Q2).** Dedicated `GET /config/features` (recommended)
+ vs. folding a `features` block into the existing `/health` payload, since
+ "what's enabled" is arguably a health concern.
+3. **Prefix & key naming (Q3).** `EN_FEATURE_*` prefix and
+ `EN_FEATURE_REPEATED_WORD_CHECK` → `repeatedWordCheck` mapping; and comfort
+ with declaring each flag var in the schema (small DRY cost, buys docs +
+ validation) vs. a generic sweep.
+4. **Default-derivation (Q4).** Deriving the unset default from AI-env presence —
+ desirable safety, or too clever (prefer a plain `false` default)?
+
+## Reviewer outcome
+
+_Pending review._ Once the four questions above are answered, the confirmed
+decisions (reviewer, date, PR comment links) will be recorded here and in
+[`feature-flags-suggestion.md`](feature-flags-suggestion.md).