Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e87fc13
docs(#1357): scripted prune inventory as committed evidence
radandevist Aug 25, 2026
b94582e
docs(#1357): move the 9 referenced records to docs/records/
radandevist Aug 25, 2026
f1e1cfb
docs(#1357): rewrite the filing index for the four-directory tree
radandevist Aug 25, 2026
664bf59
docs(#1357): delete pruned records; retarget docs-archive gate to doc…
radandevist Aug 25, 2026
c24a7c9
docs(#1357): commit RED/GREEN proof for the doc-links guard
radandevist Aug 25, 2026
9706166
docs(#1357): land merged #1355 paid-modules spec into docs/records pe…
radandevist Aug 25, 2026
d1e1652
docs(#1357): regenerate stale prune inventory (92 candidates incl. me…
radandevist Aug 25, 2026
5966b43
ci(#1357): enforce prune-inventory freshness in the docs-archive gate
radandevist Aug 25, 2026
967d53a
fix(#1357): doc-links guard scans untracked non-ignored files too (re…
radandevist Aug 25, 2026
cfd7e42
fix(#1357): guard scans docs/ path literals in code surfaces too (rev…
radandevist Aug 25, 2026
890a369
docs(#1357): align prune-inventory notes with post-rebase reality; li…
radandevist Aug 25, 2026
f727b83
docs(#1357): refresh guard-proof record to match r1-fixed guard (58 f…
radandevist Aug 25, 2026
7bbf128
fix(#1357): --check cross-validates the prune inventory against git r…
radandevist Aug 25, 2026
7b8ce98
test(#1357): pin git-fidelity gate end-to-end via planted-generator f…
radandevist Aug 25, 2026
b601ffa
chore(#1357): empty commit to retrigger CI (push event dropped by Act…
radandevist Aug 25, 2026
b068baa
Merge origin/develop into lane/wt-1357
radandevist Aug 25, 2026
c0a507c
fix(#1357): map the #1385 bulk-actions spec after develop merge; rege…
radandevist Aug 25, 2026
2cef5aa
style(#1357): oxfmt the fidelity fixture suite
radandevist Aug 25, 2026
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
21 changes: 14 additions & 7 deletions .github/workflows/docs-archive.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ on:
push:
paths:
- 'docs/**'
- 'packages/scripts-ts/src/check-archive-records.ts'
- 'packages/scripts-ts/src/check-archive-records.test.ts'
- 'packages/scripts-ts/src/check-doc-links.ts'
- 'packages/scripts-ts/src/check-doc-links.test.ts'
- '.github/workflows/docs-archive.yml'

permissions:
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
CLASSIFIER=base-ref/packages/scripts-ts/src/ci-changed-paths.ts

if [ -f "$CLASSIFIER" ]; then
node "$CLASSIFIER" '^(docs/|packages/scripts-ts/src/check-archive-records\.ts$|packages/scripts-ts/src/check-archive-records\.test\.ts$|\.github/workflows/docs-archive\.yml$)'
node "$CLASSIFIER" '^(docs/|packages/scripts-ts/src/check-doc-links\.ts$|packages/scripts-ts/src/check-doc-links\.test\.ts$|packages/scripts-ts/src/audit-docs-prune\.ts$|\.github/workflows/docs-archive\.yml$)'
else
echo "::warning::[MISSING BASE CLASSIFIER] $CLASSIFIER does not exist at the base commit. This is expected exactly once: for the pull request that introduces this classifier, or a branch cut before it existed on the base branch. Relevance cannot be determined from a classifier that is not there, so this fails closed: treating the workflow as relevant and running everything. If this keeps happening on ordinary pull requests well after the classifier has been on the base branch, something is wrong — investigate, do not silence it."
echo "relevant=true" >> "$GITHUB_OUTPUT"
Expand Down Expand Up @@ -131,11 +131,18 @@ jobs:
- name: Install (frozen lockfile, no scripts)
run: pnpm install --frozen-lockfile --ignore-scripts

- name: Run archive guard fixture tests
run: pnpm --filter scripts-ts exec vitest run src/check-archive-records.test.ts
- name: Run doc-links guard fixture tests
run: pnpm --filter scripts-ts exec vitest run src/check-doc-links.test.ts

- name: Check archive records
run: node packages/scripts-ts/src/check-archive-records.ts
- name: Check doc links
run: node packages/scripts-ts/src/check-doc-links.ts

# Round-1 review CRITICAL: the prune inventory is generated evidence,
# and a stale record must fail the gate rather than rot silently —
# --check regenerates from the pre-prune merge-base tree in memory and
# compares against HEAD's committed record (exit 1 on any drift).
- name: Check prune inventory freshness (--check)
run: node packages/scripts-ts/src/audit-docs-prune.ts --check

# Required status check: this is the one context that should be required
# on the branch ruleset instead of relying on the trigger. It always
Expand Down
21 changes: 12 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ There is **no `apps/jobs`**. Background jobs shipped inside the API project (`ap
and run as a separate deployed process off the **same API image** with `APP_ROLE=worker` — see
`dokploy.yml`.

`apps/old-front` was retired on 2026-08-22. Archive in `docs/archive/old-front`, tag `old-front-final`.
All frontend work happens in `apps/front`; `docs/archive/old-front` is the reference for the retired surfaces.
`apps/old-front` was retired on 2026-08-22 (tag `old-front-final`).
All frontend work happens in `apps/front`; the retired app's source lives only at tag `old-front-final`, not in this tree.

### Backend Architecture (Vertical Slice, Domain-First)

Expand Down Expand Up @@ -329,9 +329,9 @@ For the complete list of custom lint rules with severity and source, see [`docs/
- Loading/empty/error states use the front state components (`state-view.tsx`, `state-surface.tsx`, `skeleton.tsx`) — never ad-hoc conditional rendering per page.
- **Entity images and avatars:** preserve the real image when one exists and keep the intended aspect ratio. When there is genuinely no image, an **entity identity** surface — a person or an organization — falls back to initials on a deterministic, name-hashed colour from the `--publy-avatar-1`…`--publy-avatar-8` palette with `--publy-avatar-foreground` text (`paletteIndex()` in [`apps/front/src/components/ui/avatar-initials.ts`](apps/front/src/components/ui/avatar-initials.ts), applied via [`apps/front/src/components/ui/person-avatar.tsx`](apps/front/src/components/ui/person-avatar.tsx)). That colour is **identity, not decoration**: it is what distinguishes two photoless people in the same list and makes one person recognizable across a table row, a drawer, and the account menu — a uniform grey column carries no information at all. The palette is WCAG-pinned against fixed white text and deliberately theme-invariant, so do **not** swap it for muted tokens, and do **not** give it an `html.dark` counterpart (see `THEME_INVARIANT_TOKENS` in [`apps/front/scripts/check-design-system.mjs`](apps/front/scripts/check-design-system.mjs) and the contrast guard in [`apps/front/src/styles/avatar-fallback-contrast.test.ts`](apps/front/src/styles/avatar-fallback-contrast.test.ts)). Neutral muted tokens remain correct for fallbacks that are **not** entity identity. Build on the stable `Avatar`/`AvatarImage`/`AvatarFallback` primitive layer in [`apps/front/src/components/ui/avatar.tsx`](apps/front/src/components/ui/avatar.tsx), whose image preserves a square cover crop and whose bare fallback stays neutral for those non-identity consumers. **front has no `<Image>` primitive** — do not import one, and do not invent one as a side effect of another task; if a non-avatar content-image need appears, raise it as its own change rather than sprawling raw `<img>` tags. Raw `<img>` is acceptable only for the brand wordmark/logo and inline SVGs, as it is used today in the layouts.
- **React Doctor HARD gate:** a PR must not leave any React Doctor finding in a file it changes. Run `just react-doctor` before pushing. CI enforces this via `.github/workflows/react-doctor.yml` (`--scope files --blocking warning`). Full guide: [`docs/guides/react-doctor.md`](docs/guides/react-doctor.md).
- Bulk-action items on list-page selection menus always render — never `disabled`, never conditionally hidden by per-row eligibility; ineligible clicks show an i18n toast. The trigger button gates on `BULK_ACTION_MAX_COUNT`. See [`docs/guides/bulk-action-ux-conventions.md`](docs/guides/bulk-action-ux-conventions.md) (its backend/UX policy is normative; its old MUI-era `apps/old-front` code snippets are archived in `docs/archive/old-front`).
- Bulk-action items on list-page selection menus always render — never `disabled`, never conditionally hidden by per-row eligibility; ineligible clicks show an i18n toast. The trigger button gates on `BULK_ACTION_MAX_COUNT`. See [`docs/guides/bulk-action-ux-conventions.md`](docs/guides/bulk-action-ux-conventions.md) (its backend/UX policy is normative; its old MUI-era `apps/old-front` code snippets died with that retired app).

`apps/old-front` was retired on 2026-08-22 (archive `docs/archive/old-front`, tag `old-front-final`). The MUI/`sx` standards that governed it are archived, not deleted as guidance — see the archive for the retired patterns.
`apps/old-front` was retired on 2026-08-22 (tag `old-front-final`). The MUI/`sx` standards that governed it are archived, not deleted as guidance — see git history or that tag for the retired patterns.

**Enabled `publy/*` lint-rule scopes** (the configuration sets each of these to `error`):

Expand Down Expand Up @@ -423,7 +423,7 @@ For the complete list of custom lint rules with severity and source, see [`docs/

- Backend routes use kebab-case; constants in `RoutePath.cs` (backend) and `constants.ts` (frontend)
- Errors: `AppProblemDetails` (400/401/403/404/500) + `ValidationProblemDetails` (422) — both RFC 7807
- **Transparent failure causes (owner product rule, 2026-08-22):** every failure the backend persists or returns carries a human-readable cause and, where one exists, the next action — a `Failed`/`Paused`/`NeedsReconnect` row stores a sanitised `LastError`/cause (never a secret, never a stack trace), a job failure records the provider's classified reason, and a problem response names what went wrong in plain words. Never `Failed` with an empty reason, never a generic "something went wrong". Spec: `docs/superpowers/specs/2026-08-22-epic-d-publishing-scheduling-design.md` §1.7; UI counterpart in `DESIGN.md` (error states).
- **Transparent failure causes (owner product rule, 2026-08-22):** every failure the backend persists or returns carries a human-readable cause and, where one exists, the next action — a `Failed`/`Paused`/`NeedsReconnect` row stores a sanitised `LastError`/cause (never a secret, never a stack trace), a job failure records the provider's classified reason, and a problem response names what went wrong in plain words. Never `Failed` with an empty reason, never a generic "something went wrong". Spec: `docs/records/2026-08-22-spec-epic-d-publishing-scheduling.md` §1.7; UI counterpart in `DESIGN.md` (error states).
- Frontend/Node: use `logger` from `@org/shared-ts/lib/logger/iso-logger` (not `console.*`) (enforced by `publy/no-console-in-source`)
- Frontend API errors: centralized via `ApiFailure` discriminated union — see [`docs/guides/frontend-error-handling.md`](docs/guides/frontend-error-handling.md)
- Frontend local mutation handlers must derive user-facing error text through `getFailureMessage(toApiFailure(error), ...)`; never translate `response-message` keys manually at the call site (enforced by `publy/no-manual-response-message-translation`)
Expand Down Expand Up @@ -479,12 +479,15 @@ client regeneration workflow, and TypeScript patterns), see:

## Documentation Organization

[`docs/README.md`](docs/README.md) is the filing index: it lists which documents are normative and
gives one filing rule per directory. Read it before creating a document.
`docs/` has exactly four directories: `guides/` (standing rules), `deployment/` (production
operations), `records/` (dated, write-once records named `YYYY-MM-DD-<type>-<topic>.md`, type from
spec/plan/review/audit/spike/analysis), and `assets/`. [`docs/README.md`](docs/README.md) is the
filing index; read it before creating a document.

- **Never** place a generated doc at the repo root, and never at the `docs/` root either — always in
a `docs/` subdirectory
- Use an existing subdirectory; only create a new one (kebab-case) if nothing in `docs/README.md` fits
one of those four directories
- A new record goes to `docs/records/` under a `YYYY-MM-DD-<type>-<topic>.md` name; never create a
new top-level `docs/` directory
- This file links guides/deployment docs for standing policy and may also link repository
config/source files to anchor a rule. A `docs/guides/` file this file does not link is a record,
not a rule
Expand Down
14 changes: 7 additions & 7 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ the only handoff-specified eyebrow colour; do not lighten it. — source: `apps/
(colour), `--publy-motion-medium 240ms` (layout). — source: `app.css`
- Marketing landing adds two durations: `--publy-landing-motion-press 50ms` (direct-manipulation
feel on press) and `--publy-landing-motion-entrance 560ms` (hero/scroll-reveal arrival). These are
theme-invariant. — source: `app.css`, `docs/superpowers/specs/2026-08-01-marketing-landing-bands-design.md`
theme-invariant. — source: `app.css`, `docs/records/2026-08-01-spec-marketing-landing-bands.md`
- No bespoke easing curves outside these tokens.

### Focus rings
Expand Down Expand Up @@ -269,7 +269,7 @@ aggregations. **front has no `<Image>` primitive** — only raw `<img>` for word
because Sonner's un-layered stylesheet would defeat the app cascade. — source: `app.css`, `e2e/toast-contrast.spec.ts`

### Empty / error / loading states
- **Every failure state shows its cause in plain words and the next action; never a bare "something went wrong".** This is the owner's product UI rule (decision 2026-08-22, spec `docs/superpowers/specs/2026-08-22-epic-d-publishing-scheduling-design.md` §1.7, on branch `docs/spec-epic-c-social-accounts`). A failed or paused state names what went wrong in one short sentence and offers the concrete recovery action (Retry / Reconnect the account / Reschedule), not a generic error string. — source: owner decision 2026-08-22, `docs/superpowers/specs/2026-08-22-epic-d-publishing-scheduling-design.md` §1.7
- **Every failure state shows its cause in plain words and the next action; never a bare "something went wrong".** This is the owner's product UI rule (decision 2026-08-22, spec `docs/records/2026-08-22-spec-epic-d-publishing-scheduling.md` §1.7, on branch `docs/spec-epic-c-social-accounts`). A failed or paused state names what went wrong in one short sentence and offers the concrete recovery action (Retry / Reconnect the account / Reschedule), not a generic error string. — source: owner decision 2026-08-22, `docs/records/2026-08-22-spec-epic-d-publishing-scheduling.md` §1.7
- One shared primitive `state-view.tsx` (`StateView`, `StateSurface`, `ErrorStateSurface`,
`NoMatchStateSurface`) at a **single visual scale**: 48px tone-coloured glyph (no disc/ring/box),
matching title/description/action sizing. Full-page states use `h1`; in-list states use a non-`h1`.
Expand All @@ -282,7 +282,7 @@ aggregations. **front has no `<Image>` primitive** — only raw `<img>` for word
### Dark mode
- Toggled via `ui-store` and the `.dark` class on `<html>` (`@custom-variant dark (&:is(.dark *))`).
The toggle is **instant** (no flash). Dark values are authored at implementation time from the
`gray-ui-csm` template; light is the design canvas. — source: `conventions.md` (Navigation & layout), `app.css` (`@custom-variant dark`), `docs/archive/2026/designs/2026-07-09-front-2-gray-ui-stack-migration-design.md`
`gray-ui-csm` template; light is the design canvas. — source: `conventions.md` (Navigation & layout), `app.css` (`@custom-variant dark`), `docs/records/2026-07-09-spec-front-2-gray-ui-stack-migration.md`
- Theme-invariant tokens (avatar palette, auth panel, chrome bevel) intentionally do **not** swap. — source: `check-design-system.mjs` (`THEME_INVARIANT_TOKENS`)

### Mutation feedback
Expand All @@ -309,12 +309,12 @@ and error branches). — source: `conventions.md` (Marketing Surfaces)
- **Contrast is measured, not assumed**: `styles/marketing-contrast.test.ts` pins the pairs the shell
paints in both themes; small text on a muted surface uses `--publy-foreground-secondary`, not
`--publy-foreground-muted`. — source: `conventions.md`, `apps/front/src/styles/marketing-contrast.test.ts`
- **Landing bands** (spec `2026-08-01-marketing-landing-bands-design.md`): the landing page keeps its
- **Landing bands** (spec `docs/records/2026-08-01-spec-marketing-landing-bands.md`): the landing page keeps its
hero → claims → tour → bento → timeline → FAQ → closing flow; new bands (pricing always-on;
customer-logo and social-proof behind `FEATURES.marketing.customerLogos` / `.socialProof`, default
off) are inserted immediately before the FAQ. Off bands contribute **no** DOM. All copy uses
`landing-*` i18n keys; no new CSS tokens, raw colours, inline styles, images, avatars, or personal
names. — source: `docs/superpowers/specs/2026-08-01-marketing-landing-bands-design.md`
names. — source: `docs/records/2026-08-01-spec-marketing-landing-bands.md`
- Cookie consent fails closed (absent/malformed/old → "no optional cookies" + asks again); Accept and
Reject are equal-sized; preferences open a right-side drawer, not a centred modal; categories use
squared `Checkbox` (a fully-rounded switch track is guard-banned). — source: `conventions.md`
Expand Down Expand Up @@ -369,9 +369,9 @@ These are factual observations from the tree; this document reports them rather
There is no single enforced case rule in the tree — both coexist in `common.en.json`.
— source: `packages/shared-ts/src/lib/i18n/json/common.en.json`
2. **`conventions.md` references `apps/front` while the archive design references `apps/front-2`.**
The gray-UI migration design (`docs/archive/2026/designs/2026-07-09-front-2-gray-ui-stack-migration-design.md`)
The gray-UI migration design (`docs/records/2026-07-09-spec-front-2-gray-ui-stack-migration.md`)
describes the stack that became `apps/front`; its component list predates the current `components/ui/*`
set and is historical, not normative. — source: `docs/archive/2026/designs/2026-07-09-front-2-gray-ui-stack-migration-design.md`, `AGENTS.md`
set and is historical, not normative. — source: `docs/records/2026-07-09-spec-front-2-gray-ui-stack-migration.md`, `AGENTS.md`
3. **`--publy-foreground-subtle` is a documented intentional contrast failure** (2.5:1), permitted only
for non-text roles, yet three classes (`publy-type-helper`, `publy-field-helper`, `publy-type-eyebrow`)
are pinned to it by the drawer-description guard as intentional exceptions. The token is therefore
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ pnpm --filter front dev
The copied template already targets the local Compose database. Keep its local development values
unless you intentionally run a different local database.

> Use `pnpm --filter front <script>` or `just ci-front` for the frontend. `apps/old-front` was retired on 2026-08-22 (archive `docs/archive/old-front`, tag `old-front-final`).
> Use `pnpm --filter front <script>` or `just ci-front` for the frontend. `apps/old-front` was retired on 2026-08-22 (tag `old-front-final`).

> After creating and editing `.env.development`, `just dev-setup` can run install + database in one
> step. Its final prompt — and the final prompt from `just quick-start` — says to run
Expand Down Expand Up @@ -367,7 +367,7 @@ Quality gates also run automatically on commit via Husky. See
- **Frontend (front)** — [front/index](docs/guides/front/index.md),
[front/conventions](docs/guides/front/conventions.md),
[frontend-error-handling](docs/guides/frontend-error-handling.md)
(its `ApiFailure` contract is normative; its code examples were MUI-era `apps/old-front`, now archived in `docs/archive/old-front`)
(its `ApiFailure` contract is normative; its code examples were MUI-era `apps/old-front`, retired with the app)
- **Contracts & workflows** — [openapi-kiota-safeguards](docs/guides/openapi-kiota-safeguards.md),
[common-workflows](docs/guides/common-workflows.md),
[project-conventions](docs/guides/project-conventions.md)
Expand All @@ -376,7 +376,7 @@ A few non-negotiables worth surfacing here:

- Frontend work means **`apps/front`**: `@base-ui/react` primitives behind a local
`components/ui/*` layer, styled with **Tailwind v4**. No MUI, no `sx`.
- **`apps/old-front` was retired on 2026-08-22.** Archive in `docs/archive/old-front` (tag `old-front-final`). It is not built, not deployed, and must not be edited or copied.
- **`apps/old-front` was retired on 2026-08-22** (tag `old-front-final`). It is not built, not deployed, and must not be edited or copied.
- Backend errors are **RFC 7807** (`application/problem+json`); `401` means "session invalid" only.
- Add new backend code under domain modules in `apps/api/Modules/<Domain>/` — not the legacy folders.
- Regenerate, never hand-edit, the API client (see [API Contract Workflow](#api-contract-workflow)).
Expand Down
4 changes: 2 additions & 2 deletions apps/front/e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ docker compose -f apps/front/docker-compose.test.yml down -v
## Running by tag

Every top-level `test.describe` carries `@<domain>` and `@<ticket>` tags
(vocabulary in [`docs/guides/e2e-tags.md`](../../docs/guides/e2e-tags.md)).
(vocabulary in [`docs/guides/e2e-tags.md`](../../../docs/guides/e2e-tags.md)).
Use the `test:e2e:tag` script to filter:

```bash
Expand All @@ -47,7 +47,7 @@ and fails when a top-level `test.describe` lacks a domain tag, uses a domain
outside the vocabulary, or lacks a ticket tag. Adding a new spec without tags
breaks the gate.

See [`docs/guides/e2e-coverage.md`](../../docs/guides/e2e-coverage.md) for
See [`docs/guides/e2e-coverage.md`](../../../docs/guides/e2e-coverage.md) for
when to write an e2e test (the five criteria).

## Notes
Expand Down
Loading
Loading