Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .glossary/TERMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ This file names the *what*.
| bildirim | The notification **feature** (`features/bildirim`, epic #1666): the recipient-keyed store + read model behind the `/bildirimler` center page and the topbar **unread badge** — the `notification` D1 table, the `Notification` domain service, and the `bildirim.*` fate views/mutations (`list` / `unreadCount` / `markRead` / `markAllRead`). Ships dark behind the single default-off `phoenix-bildirim` flag every emitter sibling reuses. Turkish for "notification". | the **report (bildir)** feature (content-reporting for moderation — `bildir`, a different lexeme and module); an emitter (siblings #1695–#1699 produce notifications; this is the spine) |
| divan | The proving-ground reviewer surface (`features/divan`): the gated destination where the established community (yazar + moderatör) reviews a çaylak's sandboxed work and promotes (mod-direct) or vouches (kefil) them toward yazar — work "goes before the divan". Reads the `sandboxBacklogWhere` backlog over a yazar-OR-mod gate. Its topbar entry (`divanTo` NavLink, `Topbar.tsx`) is a **destination** under the ratified nav element taxonomy (destination / primary action / utility / signal — wayfinder map #2583, fork #2586). Turkish for "council/court". | a widening of inline çaylak visibility — it is a gated **destination** that reads the sandbox backlog, not a change to who sees sandboxed items inline; not a **primary action** (it goes nowhere near the `+ gönderi` CTA class) |
| divan section nav (boxed-pill tabs) | The intra-`/divan` section switcher (`.kp-divan__nav-tab`, `DivanPage.tsx` / `Divan.css`): the çaylaklar (roster) ↔ raporlar (mod queue) tab row shown when `raporlarVisible`. Each tab is a **boxed pill** — `1px` border + `r-sm` + a resting `surface` fill, switching to an `accent-faint` fill + `accent` border on `aria-current`. Under the ratified nav taxonomy (map #2583, fork #2586) these are intra-destination **destination** switchers, and the map flags the **resting boxed fill** as a containment-law violation candidate (the nav-surface inventory #2585): a section tab may paint accent on the *active* state (transient state paint is legal) but should not carry a boxed resting chrome. | the divan topbar entry (that is the destination-level `divanTo` link, one level up); a containment-legal transient accent (the *violation* is the resting boxed fill, not the active-state accent) |
| funnel / conversion funnel | The conversion-funnel readout (`features/funnel`, #1589): the mod-gated, dark-shipped surface that measures the human **çaylak→yazar** journey. Its tracer read is the **tier population** — a humans-only (`user.type = 'human'`) `GROUP BY tier` count of how many accounts sit at each rung of the authorship ladder (`FunnelSummary`, the `funnel.summary` fate view). Served behind the default-off `phoenix-funnel-readout` flag, read-gated by the `ViewFunnel` capability (founder/mod). The Phase-2 rate/time metrics extend this same service. | a public metrics page (founder/mod only, flag-gated); a per-product stat (that is **stats**, the landing counts); an agent/bot metric (humans-only by construction) |
| funnel / conversion funnel | The conversion-funnel readout (`features/funnel`, #1589): the mod-gated, dark-shipped surface that measures the human **çaylak→yazar** journey. Its tracer read is the **tier population** — a humans-only (`user.type = 'human'`) `GROUP BY tier` count of how many accounts sit at each rung of the authorship ladder (`FunnelSummary`, the `funnel.summary` fate view). Read-gated by the `ViewFunnel` capability (founder/mod). The Phase-2 rate/time metrics extend this same service. | a public metrics page (founder/mod only); a per-product stat (that is **stats**, the landing counts); an agent/bot metric (humans-only by construction) |
| depo | kampus's **internal asset store / CDN** (designed — ADR 0144, supersedes 0044's imge; **not built**). A decoupled infra primitive, never user-facing: its own `infra/depo` alchemy stack, R2-backed, public-read at `depo.kamp.us` (capability-URL — GitHub-embeddable ⇒ never read-sensitive), writes through a doorman worker (pasaport `apiKey`, content-addressed write-once), client is a standalone `depo` CLI. Turkish for "depot/warehouse". First consumer: agent Playwright screenshots → GitHub PR descriptions (via the review-ui skill). Renamed from **imge** (the imgur-style scope is rejected). | the imgur-style public image/video product (that scope is **rejected**, ADR 0144) |
| pano | HN-style link & discussion aggregator with threaded comments. Turkish for "board". | "board", "pinboard" |
| pasaport | Identity/auth/profiles/karma domain; wraps better-auth. Turkish for "passport". | "auth" (broader) |
Expand Down
4 changes: 0 additions & 4 deletions apps/web/alchemy.run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import {
emailDeliveryAdminFlag,
emailDeliveryNoticeFlag,
Flagship,
funnelReadoutFlag,
karmaGatesFlag,
mecmuaFeedFlag,
mecmuaPublicReadFlag,
Expand Down Expand Up @@ -92,9 +91,6 @@ export default Alchemy.Stack(
// The earned-authorship loop (çaylak→yazar) dark-ship flag, default-off
// (#1204, epic #1202) — the single seam the authorship-loop epic gates behind.
yield* authorshipLoopFlag(flagship.appId);
// The conversion-funnel readout dark-ship flag, default-off (#1589) — the
// founder/mod tier-count surface gates behind this key until a human release.
yield* funnelReadoutFlag(flagship.appId);
// The optimistic in-place content-edit dark-ship flag, default-off (#1675,
// epic #1637) — post/comment/definition edits pass an optimistic payload only
// behind this key until a human release.
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,8 @@ export function App() {
)}
<Route path="/search" element={<SearchPage />} />
<Route path="/auth" element={<AuthPage />} />
{/* The founder/mod conversion readout (#1589) — the page self-gates on
the funnel-readout flag (off ⇒ 404), so the route is dark by default. */}
{/* The founder/mod conversion readout (#1589) — access is server-authoritative
(the gated funnel.summary read denies a non-mod), so the route stays mod-only. */}
<Route path="/funnel" element={<FunnelPage />} />
{/* The notification center (#1694) — the page self-gates on the
phoenix-bildirim flag (off ⇒ 404), so the route is dark by default. */}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/funnel/FunnelSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
* the promotion rate (#1593), the first-contribution rate (#1591), and the vouch rate
* (#1592) — over the two tier counts (çaylak, yazar) the founder/mod front page
* renders. Reads the gated
* `funnel.summary` DESTINATION (founder/mod, behind `phoenix-funnel-readout`); a
* non-mod read denies the invisible `UNAUTHORIZED`, caught by the page's `<Screen>`.
* `funnel.summary` DESTINATION (founder/mod only); a non-mod read denies the
* invisible `UNAUTHORIZED`, caught by the page's `<Screen>`.
*
* a11y (#1202 baseline): each headline rate is a labelled figure (`<figure>` +
* `<figcaption>`), and the counts are a real description list (`<dl>`) so each number
Expand Down
20 changes: 0 additions & 20 deletions apps/web/src/components/funnel/funnelGating.test.ts

This file was deleted.

21 changes: 0 additions & 21 deletions apps/web/src/components/funnel/funnelGating.ts

This file was deleted.

13 changes: 1 addition & 12 deletions apps/web/src/flags/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,6 @@ export const PHOENIX_AUTHORSHIP_LOOP = "phoenix-authorship-loop";
*/
export const PHOENIX_BILDIRIM = "phoenix-bildirim";

/**
* Conversion-funnel readout dark-ship flag (#1589). The founder/mod aggregate
* tier-count surface (`/funnel` + the `funnel.summary` read) gates behind this key;
* default-off so the readout reaches production dark until a human flips it at
* release (ADR 0083). Its OWN key, not the `phoenix-authorship-loop` seam — the
* funnel is a separate mod-only destination with its own lifecycle.
*/
export const PHOENIX_FUNNEL_READOUT = "phoenix-funnel-readout";

/**
* Optimistic in-place content-edit dark-ship flag (#1675, epic #1637). Gates the
* three Class-A content edits (`post.edit`, `comment.edit`, `definition.edit`)
Expand Down Expand Up @@ -167,8 +158,7 @@ export const PHOENIX_KARMA_GATES = "phoenix-karma-gates";
* until a human flips it at release (ADR 0083): with it off the mutations/read fail
* the invisible `Denied` (like a non-admin call) and the client controls render
* nothing, so no session is ever refused by an unreleased feature. Its OWN key, not
* a shared authz seam — ban is a distinct admin capability with its own lifecycle
* (the `phoenix-funnel-readout` mod-surface precedent).
* a shared authz seam — ban is a distinct admin capability with its own lifecycle.
*/
export const PHOENIX_USER_BAN = "phoenix-user-ban";

Expand Down Expand Up @@ -304,7 +294,6 @@ export const DECLARED_FLAGS: readonly FlagDeclaration[] = [
{key: MECMUA_FEED, defaultValue: false},
{key: PHOENIX_AUTHORSHIP_LOOP, defaultValue: false},
{key: PHOENIX_BILDIRIM, defaultValue: false},
{key: PHOENIX_FUNNEL_READOUT, defaultValue: false},
{key: PHOENIX_OPTIMISTIC_EDITS, defaultValue: false},
{key: PHOENIX_OPTIMISTIC_DEFINITION_ADD, defaultValue: false},
{key: PHOENIX_OPTIMISTIC_DEFINITION_DELETE, defaultValue: false},
Expand Down
24 changes: 1 addition & 23 deletions apps/web/src/pages/FunnelPage.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
/**
* `FunnelPage` — the `/funnel` founder/mod conversion readout (#1589): the tracer
* bullet for the çaylak→yazar metrics, today just the current tier population. The
* whole surface ships dark behind the `phoenix-funnel-readout` flag: with the flag
* off the route renders the 404 (effectively absent); loading shows a neutral
* placeholder so the 404 never flashes before the flag resolves.
* bullet for the çaylak→yazar metrics, today just the current tier population.
*
* Access is SERVER-authoritative — the gated `funnel.summary` read denies a non-mod
* the invisible `UNAUTHORIZED` (`requireFunnelAccess`). The `<Screen>` catches that
* and renders the "yetkin yok" state; no client-side authority guess decides who
* may enter, mirroring `DivanPage`.
*/
import {FunnelSummary} from "../components/funnel/FunnelSummary";
import {shouldRenderFunnelPage} from "../components/funnel/funnelGating";
import {Screen} from "../fate/Screen";
import {PHOENIX_FUNNEL_READOUT} from "../flags/keys";
import {useFlag} from "../flags/useFlag";
import {NotFoundPage} from "./NotFoundPage";
import "../components/funnel/Funnel.css";

export function FunnelPage() {
const {value: flagOn, loading: flagLoading} = useFlag(PHOENIX_FUNNEL_READOUT, false);

// Don't decide 404-vs-page until the flag resolves, or the 404 flashes first.
if (flagLoading) {
return (
<div className="kp-funnel">
<div className="kp-funnel__inner">
<p className="kp-funnel__loading">yükleniyor…</p>
</div>
</div>
);
}

if (!shouldRenderFunnelPage(flagOn)) return <NotFoundPage />;

return (
<main className="kp-funnel" data-testid="funnel-page">
<div className="kp-funnel__inner">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* The dark-ship default-=-safe-state invariant for the bildirim (notification
* system) flag (#1694, epic #1666). Inspected off the exported `BILDIRIM_FLAG`
* record (the same object the factory spreads into `FlagshipFlag`), so no alchemy
* resource is constructed — mirrors `funnel-readout.invariant.test.ts`.
* resource is constructed — mirrors `authorship-loop.invariant.test.ts`.
*/
import {assert, describe, it} from "@effect/vitest";
import {PHOENIX_BILDIRIM} from "../../../src/flags/keys.ts";
Expand Down
26 changes: 0 additions & 26 deletions apps/web/worker/features/flagship/funnel-readout.invariant.test.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* content-edit flag (#1675, epic #1637). Inspected off the exported
* `OPTIMISTIC_EDITS_FLAG` record (the same object the factory spreads into
* `FlagshipFlag`), so no alchemy resource is constructed — mirrors
* `funnel-readout.invariant.test.ts` (#1589).
* `authorship-loop.invariant.test.ts` (#1204).
*/
import {assert, describe, it} from "@effect/vitest";
import {PHOENIX_OPTIMISTIC_EDITS} from "../../../src/flags/keys.ts";
Expand Down
37 changes: 1 addition & 36 deletions apps/web/worker/features/flagship/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
PHOENIX_EDGE_SHELL_BOOT,
PHOENIX_EMAIL_DELIVERY_ADMIN,
PHOENIX_EMAIL_DELIVERY_NOTICE,
PHOENIX_FUNNEL_READOUT,
PHOENIX_KARMA_GATES,
PHOENIX_NAV_IA,
PHOENIX_OPTIMISTIC_DEFINITION_ADD,
Expand Down Expand Up @@ -98,7 +97,6 @@ export {
PANO_DRAFT_SAVE,
PHOENIX_AUTHORSHIP_LOOP,
PHOENIX_BILDIRIM,
PHOENIX_FUNNEL_READOUT,
PHOENIX_KARMA_GATES,
PHOENIX_OPTIMISTIC_DEFINITION_ADD,
PHOENIX_OPTIMISTIC_EDITS,
Expand Down Expand Up @@ -274,39 +272,6 @@ export const authorshipLoopFlag = (appId: Input<string>) =>
rules: AUTHORSHIP_LOOP_RULES,
});

/**
* The conversion-funnel readout dark-ship flag config (#1589) — the founder/mod
* tier-count surface gates behind this key. Default-OFF so the readout reaches
* production dark; flipping it on is the human release act (ADR 0083). Its own key
* (not `phoenix-authorship-loop`) so the funnel destination has an independent
* lifecycle.
*
* Exported as a plain object so the default-=-safe-state invariant is
* unit-inspectable WITHOUT constructing the alchemy resource (mirrors
* `PANO_DRAFT_SAVE_FLAG`, #746).
*
* Per-flag metadata (the IaC ownership record `feature-flags-schema-lifecycle.md`
* asks for):
* - owner: funnel (the conversion-readout founder/mod surface)
* - originating: #1589 (the çaylak→yazar conversion readout)
* - removal trigger: once the funnel readout graduates to on at 100% and stable
* for one release, retire the flag and inline the surface.
*/
export const FUNNEL_READOUT_FLAG = {
key: PHOENIX_FUNNEL_READOUT,
description:
"conversion-funnel readout dark-ship (#1589). owner: funnel. removal: retire once on at 100% and stable.",
defaultVariation: "off",
variations: {off: false, on: true},
} as const;

/**
* A plain boolean kill-switch, no targeting rules. `appId` is resolved at deploy
* (see `demoTargetingFlag` for why it's a factory, not a module constant).
*/
export const funnelReadoutFlag = (appId: Input<string>) =>
Cloudflare.Flagship.Flag("phoenix_funnel_readout", {appId, ...FUNNEL_READOUT_FLAG});

/**
* The optimistic in-place content-edit dark-ship flag config (#1675, epic #1637).
* The three Class-A content edits (`post.edit`/`comment.edit`/`definition.edit`)
Expand Down Expand Up @@ -532,7 +497,7 @@ export const karmaGatesFlag = (appId: Input<string>) =>
* the human release act (ADR 0083).
*
* Exported as a plain object so the default-=-safe-state invariant is
* unit-inspectable WITHOUT constructing the alchemy resource (mirrors `FUNNEL_READOUT_FLAG`).
* unit-inspectable WITHOUT constructing the alchemy resource (mirrors `PANO_DRAFT_SAVE_FLAG`).
*
* Per-flag metadata (`feature-flags-schema-lifecycle.md`):
* - owner: pasaport (the identity + session-boundary surface)
Expand Down
4 changes: 2 additions & 2 deletions apps/web/worker/features/funnel/fate-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {queries} from "./queries.ts";
import {funnelSummaryDataView} from "./views.ts";

const roots: FateRootsRecord = {
// The conversion-funnel readout (#1589) — founder/mod-gated, behind the
// `phoenix-funnel-readout` flag; the `funnel.summary` resolver owns the gate.
// The conversion-funnel readout (#1589) — founder/mod-gated; the
// `funnel.summary` resolver owns the capability gate.
"funnel.summary": funnelSummaryDataView,
};

Expand Down
Loading
Loading