@ttt-productions/dev-standards 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/AGENTS.base.md +17 -0
  2. package/claude/CLAUDE.base.md +263 -0
  3. package/config/copies/.gitattributes +1 -0
  4. package/config/copies/.nvmrc +1 -0
  5. package/config/copies/components.json +21 -0
  6. package/config/prettier.json +4 -0
  7. package/config/tsconfig.app.json +26 -0
  8. package/config/tsconfig.functions.json +18 -0
  9. package/development-preferences.md +170 -0
  10. package/dist/node/a11y.d.ts +26 -0
  11. package/dist/node/a11y.d.ts.map +1 -0
  12. package/dist/node/a11y.js +44 -0
  13. package/dist/node/a11y.js.map +1 -0
  14. package/dist/node/eslint.d.ts +11 -0
  15. package/dist/node/eslint.d.ts.map +1 -0
  16. package/dist/node/eslint.js +137 -0
  17. package/dist/node/eslint.js.map +1 -0
  18. package/dist/node/playwright.d.ts +8 -0
  19. package/dist/node/playwright.d.ts.map +1 -0
  20. package/dist/node/playwright.js +26 -0
  21. package/dist/node/playwright.js.map +1 -0
  22. package/dist/node/postcss.d.ts +5 -0
  23. package/dist/node/postcss.d.ts.map +1 -0
  24. package/dist/node/postcss.js +4 -0
  25. package/dist/node/postcss.js.map +1 -0
  26. package/dist/node/vitest.d.ts +15 -0
  27. package/dist/node/vitest.d.ts.map +1 -0
  28. package/dist/node/vitest.js +76 -0
  29. package/dist/node/vitest.js.map +1 -0
  30. package/model-tiers.md +26 -0
  31. package/package.json +123 -5
  32. package/playbooks/README.md +135 -0
  33. package/playbooks/async-workflow-playbook.md +183 -0
  34. package/playbooks/auth-boundary-playbook.md +209 -0
  35. package/playbooks/callable-functions-playbook.md +219 -0
  36. package/playbooks/client-write-security-audit-playbook.md +147 -0
  37. package/playbooks/codebase-hygiene-playbook.md +1480 -0
  38. package/playbooks/crosswalk.md +204 -0
  39. package/playbooks/data-integrity-playbook.md +161 -0
  40. package/playbooks/dependency-audit-playbook.md +188 -0
  41. package/playbooks/field-lineage-playbook.md +91 -0
  42. package/playbooks/firestore-storage-rules-playbook.md +182 -0
  43. package/playbooks/media-pipeline-playbook.md +166 -0
  44. package/playbooks/migration-playbook.md +163 -0
  45. package/playbooks/observability-playbook.md +168 -0
  46. package/playbooks/package-boundaries-playbook.md +181 -0
  47. package/playbooks/performance-audit-playbook.md +154 -0
  48. package/playbooks/react-leak-audit-playbook.md +147 -0
  49. package/playbooks/testing-coverage-playbook.md +169 -0
  50. package/playbooks/ux-audit-playbook.md +180 -0
  51. package/rules/README.md +103 -0
  52. package/rules/architecture/core-rules.md +103 -0
  53. package/rules/architecture/data-and-contracts.md +119 -0
  54. package/rules/architecture/packages-and-boundaries.md +147 -0
  55. package/rules/backend/background-work-and-external-effects.md +162 -0
  56. package/rules/backend/core-rules.md +153 -0
  57. package/rules/backend/firestore-storage-and-transactions.md +249 -0
  58. package/rules/backend/sensitive-writes-and-audit.md +145 -0
  59. package/rules/comments/core-rules.md +154 -0
  60. package/rules/core-rules.md +150 -0
  61. package/rules/frontend/core-rules.md +117 -0
  62. package/rules/frontend/data-and-callable-access.md +155 -0
  63. package/rules/frontend/interaction-accessibility-and-motion.md +126 -0
  64. package/rules/media/core-rules.md +153 -0
  65. package/rules/media/serving-and-rendering.md +34 -0
  66. package/rules/quality/core-rules.md +166 -0
  67. package/rules/quality/documentation-and-drift.md +61 -0
  68. package/rules/quality/reliability-and-observability.md +113 -0
  69. package/runbooks/README.md +35 -0
  70. package/runbooks/postmortem-template.md +74 -0
  71. package/work/README.md +67 -0
  72. package/work/do-later-README.md +15 -0
  73. package/workflows/README.md +80 -0
  74. package/workflows/chatgpt/code-review-sweep.md +102 -0
  75. package/workflows/chatgpt/go-through-findings.md +67 -0
  76. package/workflows/chatgpt/investigate-an-error.md +53 -0
  77. package/workflows/chatgpt/recheck-fixes.md +38 -0
  78. package/workflows/chatgpt/whole-site-walkthrough.md +77 -0
  79. package/workflows/claude/fan-out-implementation.md +43 -0
  80. package/workflows/claude/fix-known-errors-inline.md +33 -0
  81. package/workflows/claude/implement-an-approved-plan.md +35 -0
  82. package/workflows/claude/investigate-an-error.md +39 -0
  83. package/workflows/claude/mechanical-fan-out.md +38 -0
  84. package/workflows/claude/playbook-audit-run.md +35 -0
  85. package/workflows/claude/recheck-chatgpt-plan.md +56 -0
  86. package/workflows/claude/work-a-project-step.md +32 -0
  87. package/README.md +0 -3
package/AGENTS.base.md ADDED
@@ -0,0 +1,17 @@
1
+ # Codex instructions — shared base
2
+
3
+ Every repo's `AGENTS.md` is a pointer to this file and to its values. The placeholder values
4
+ (`<APP_NAME>`, `<DEV_STANDARDS>`, …) that this file and every shared doc use are declared once, in
5
+ the values block at the top of the repo's `CLAUDE.md`; read each placeholder as its value there.
6
+
7
+ Read this repository's `CLAUDE.md` before reviewing, planning, editing, or running commands, and
8
+ everything it names as required reading. Codex does not follow `CLAUDE.md`'s `@` import line, so it
9
+ also reads the shared base that line imports, `<DEV_STANDARDS>/claude/CLAUDE.base.md`, directly —
10
+ with it, `<DEV_STANDARDS>/development-preferences.md` (how an AI works with DJ), the shared rulebook
11
+ (`<DEV_STANDARDS>/rules/`), and the app's own rulebook (`<APP_RULES>`). All of it applies to Codex
12
+ exactly as written, except the parts addressed to a Claude Code session only; nothing is restated
13
+ here.
14
+
15
+ Codex-specific: operating the hosted E2E suite — run types, kickoff line, and the Codex launch
16
+ transport and supervision rules — is `<HOSTED_E2E_DOC>` (§ Commands → ChatGPT / Codex). A repo whose
17
+ `<HOSTED_E2E_DOC>` is none has no hosted suite.
@@ -0,0 +1,263 @@
1
+ # Shared base — every repo's `CLAUDE.md`
2
+
3
+ Each repo's `CLAUDE.md` holds its values block, its own facts, and one import of this file. This
4
+ file holds what every repo shares: the Claude Code preferences pointer, the mandatory reading, the
5
+ `docs/work/` and project rules, the shared-package release and adoption steps, and the Normal Test
6
+ and Dev Flows. ttt-packages imports it too.
7
+
8
+ ## Placeholders
9
+
10
+ The shared docs name repo-specific values by placeholder. The repo's `CLAUDE.md` declares every
11
+ value once, in a block at its top, and every shared doc's placeholders are read as those values;
12
+ nothing is rewritten at install time. A value of **none** means the repo has no such thing, and a
13
+ sentence or section that needs it does not apply there. The placeholders the AI docs use:
14
+
15
+ | Placeholder | Meaning |
16
+ |---|---|
17
+ | `<APP_NAME>` | The product's name as prose, flow names, and kickoff lines use it. |
18
+ | `<APP_REPO>` | The app's checkout folder. |
19
+ | `<PACKAGES_REPO>` | The ttt-packages checkout folder (in ttt-packages: this repo). |
20
+ | `<DEV_STANDARDS>` | This package's root as the repo reads it: the installed copy in an app, the source folder in ttt-packages. |
21
+ | `<APP_RULES>` | The app's own rulebook folder: its business rules and its additions and overrides on shared rules. |
22
+ | `<APP_DATA_PACKAGE>` | The app's application-data package, which follows the app's rulebook. |
23
+ | `<ADOPTION_APP>` | The app's folder name under `<PACKAGES_REPO>/docs/work/adoptions/`. |
24
+ | `<INSTALL_FOLDERS>` | Every consuming folder besides the root that declares an `@ttt-productions/*` package. |
25
+ | `<DESIGN_DOCS>` | The design docs a task reads for the system it touches. |
26
+ | `<TERMINOLOGY_DOC>` | The doc that holds the settled terminology. |
27
+ | `<DEV_E2E_LAUNCH>` | Where the repo writes how `dev:e2e` is launched on this machine. |
28
+ | `<HOSTED_E2E_DOC>` | The hosted end-to-end run doc. |
29
+ | `<HOSTED_DEV_URL>` | The hosted dev site a walkthrough browses. |
30
+ | `<APP_WORKFLOWS>` | The app's own folder for a workflow only that app has. |
31
+ | `<REFERENCE_PRODUCT>` | The other product whose mechanisms this one adapts, with its checkout folder; read only on DJ's grant (the workspace router). |
32
+
33
+ ### Rule and playbook placeholders
34
+
35
+ The shared rules and playbooks use these too; each has a value in the same block.
36
+
37
+ | Placeholder | Meaning |
38
+ |---|---|
39
+ | `<CANONICAL_ORIGIN>` | The app's one canonical public origin; every link, callback, and redirect uses it. |
40
+ | `<HOLDS_LIVE_DATA>` | **yes** once the app's database holds data that must survive (no more wipes), **no** before. |
41
+ | `<MEMBER_ACTOR_MODE>` | The audit actor mode for a member acting inside a shared resource they belong to. |
42
+ | `<CALLABLE_DECLARATIONS>` | The one file that declares every callable the client invokes. |
43
+ | `<CALLABLE_POLICY>` | The app's one callable policy: its error taxonomy, describe function, classifier, and predicates. |
44
+ | `<CALLABLE_ERROR_EXIT>` | The app-local catch-block exit every callable's error path goes through. |
45
+ | `<DESIGNED_ANSWER_PREDICATE>` | The one backend predicate that decides a designed callable refusal. |
46
+ | `<QUERY_KEYS_MODULE>` | The one file holding every query-key builder. |
47
+ | `<DOMAIN_EVENT_REGISTRY>` | The one file that owns cache invalidation; mutation hooks notify it of their domain event. |
48
+ | `<SESSION_RETIREMENT_OWNER>` | The one owner that retires a session's listeners, reads, and query cache when it ends. |
49
+ | `<QUERY_ERROR_REPORTER>` | The one query-layer monitoring capture for read failures. |
50
+ | `<READ_STATE_MODULE>` | The module that renders a read's pending, failed, empty, and content states. |
51
+ | `<LOAD_ERROR_STATE>` | The one rendered read-failure component. |
52
+ | `<LOADING_STATE>` | The one canonical pending-state component. |
53
+ | `<INTERACTIVE_PENDING_LAYER>` | The shared layer for per-row and unmount-surviving pending state. |
54
+ | `<APP_ASYNC_ACTION>` | The hook that runs async work that is not a mutation, with its pending and failure. |
55
+ | `<ASYNC_ACTION_ERROR_HANDLER>` | The capture owner for an app async action's failure. |
56
+ | `<AUTH_FAILURE_CAPTURE>` | The capture owner for a Firebase Auth operation's failure. |
57
+ | `<APP_TELEMETRY_POLICY>` | The app's telemetry content policy in `<APP_DATA_PACKAGE>`: its diagnostic keys and context. |
58
+ | `<NAVIGATION_HELPER>` | The one in-app navigation helper: its link, `navigate()`, hard navigation, and reload. |
59
+ | `<UPLOAD_PATH_BUILDER>` | The staging upload path builder in `<APP_DATA_PACKAGE>`. |
60
+ | `<MEDIA_SPECS>` | The per-origin media spec map in `<APP_DATA_PACKAGE>`. |
61
+ | `<FILE_ORIGIN_SCHEMA>` | The schema of canonical file origins in `<APP_DATA_PACKAGE>`. |
62
+ | `<PROCESSOR_ROUTER>` | The function that routes an upload to its processor. |
63
+ | `<GUARD_SUITE>` | The command that runs the repo's guard tests; `-t "<title>"` runs one. |
64
+ | `<SERVER_MODULE_GLOBS>` | `rg` exclusion globs for the app's server-only module trees under `src`. |
65
+ | `<FUNCTIONS_TEST_SAMPLE>` | One folder under `functions/src` that holds tests, for the run-location check. |
66
+ | `<APP_SCHEMA_DIR>` | The source folder of `<APP_DATA_PACKAGE>`'s document schemas. |
67
+ | `<FIELD_LINEAGE>` | The command that prints a field's lineage. |
68
+ | `<COMMENT_TREES>` | The space-separated trees the comment audit reads. |
69
+ | `<APP_VOCABULARY_PATTERN>` | A regex of the app's domain words, which no generic package may contain. |
70
+
71
+ **Additions and overrides.** A repo adds to a shared doc in its own file, under an
72
+ "`<APP_NAME>` additions" heading, and overrides shared text only explicitly: "Overrides shared
73
+ `<doc>` § X: …". An unstated difference is drift.
74
+
75
+ ## Claude Code preferences
76
+
77
+ If this is a Claude Code session, read and FOLLOW the preferences in
78
+ `~/.claude/projects/C--DjDev/memory/MEMORY.md` (Claude-Code-only; Claude web has its own).
79
+
80
+ ## Mandatory reading — every conversation
81
+
82
+ The engineering rules govern how the software is built. The shared rulebook
83
+ (`<DEV_STANDARDS>/rules/`) holds every rule the products and packages share, each stating its scope;
84
+ the app's own rulebook (`<APP_RULES>`) holds only its business rules and its additions and overrides
85
+ on shared rules, and `<APP_DATA_PACKAGE>` follows it. Rules are not optional and never "remembered"
86
+ from a past task.
87
+
88
+ - **Every conversation**: read `<DEV_STANDARDS>/development-preferences.md` — how an AI works with
89
+ DJ. Nothing in it is repeated here.
90
+ - **Every conversation** — review, investigation, planning, docs, testing, or implementation: read
91
+ the shared `<DEV_STANDARDS>/rules/README.md` and `<DEV_STANDARDS>/rules/core-rules.md`, then the
92
+ app's own `<APP_RULES>README.md` and `<APP_RULES>core-rules.md`, before acting.
93
+ - **Focused work**: also read the routed area files per the shared README's routing table —
94
+ `architecture/`, `backend/`, `frontend/`, `media/`, `quality/`, `comments/` — in both rulebooks.
95
+ Tasks route to every area they touch (a media upload change routes to all six; a CSS-only fix
96
+ routes to frontend + quality + comments).
97
+ - **Work involving a system**: read `<DESIGN_DOCS>` for the system model, then inspect the current
98
+ code — code is the implementation truth. Code/doc disagreement is surfaced, never silently resolved
99
+ (RULE-STD-ENG-008).
100
+ - **Spawning any agent (RULE-STD-ENG-010)**: every prompt that spawns an implementation, design, or
101
+ review agent MUST name that agent's mandatory reading — the routed rule files of both rulebooks for
102
+ its task area plus the relevant `<DESIGN_DOCS>`, as explicit read-before-acting paths. An
103
+ under-briefed agent requests its reading list rather than proceeding; the orchestrator owns the
104
+ violation. Every review agent's prompt also requires it to check each new or changed test against
105
+ the business rule it claims to prove and to report a test written to pass (RULE-STD-QUALITY-001;
106
+ `<DEV_STANDARDS>/playbooks/testing-coverage-playbook.md`).
107
+
108
+ The repo's `CLAUDE.md` is a lightweight entry point: values, repo context, commands, environment
109
+ facts, and boundaries. It is not a rulebook — the engineering rules own every must/never engineering
110
+ requirement.
111
+
112
+ ## Documentation and work
113
+
114
+ ALL work to do — findings, decisions waiting on DJ, work waiting to be implemented, parked work,
115
+ blocked work, human actions, and projects — lives in the repo's `docs/work/`;
116
+ `<DEV_STANDARDS>/work/README.md` says how work moves between its folders. The development workflows
117
+ (named, AI-specific conversation types DJ starts) live in `<DEV_STANDARDS>/workflows/`. Read the
118
+ relevant docs before working an area.
119
+
120
+ A design ruling is written to its owning doc the same session: built → its design doc
121
+ (`<DESIGN_DOCS>`); not yet built → `docs/work/do-later/`. Settled terminology lives in
122
+ `<TERMINOLOGY_DOC>`.
123
+
124
+ **Projects.** A project's `docs/work/projects/<project>/PLAN.md` is the living source of truth for
125
+ that project's work. Steps are NAMED (never numbered, never renumbered), executed top-to-bottom; a
126
+ step that needs one has a folder beside the plan at `docs/work/projects/<project>/<step-name>/`.
127
+ Before starting a project's work:
128
+
129
+ 1. Read the project's `PLAN.md` and find the step DJ named (or ask which step).
130
+ 2. Read that step's folder before designing — the work doc is the detail; the plan is only the map.
131
+ 3. Work one step (or one focused slice of a step) per conversation — context limits force messy
132
+ partial results otherwise.
133
+ 4. When an item completes, delete it from the step's work doc. When a step completes, delete its
134
+ folder AND its section in the project's `PLAN.md`. Never renumber anything — there are no numbers.
135
+ 5. Never add items to a project's `PLAN.md` from a conversation unless they are scope expansion for
136
+ the current step.
137
+ 6. `[OVERNIGHT-OK]` steps may relax the no-commit rule ONLY per the protocol written in that step's
138
+ work doc (dedicated branch, checkpoint commits, never main, never push, never publish).
139
+
140
+ ## Shared-package releases and adoption
141
+
142
+ ### Adoption files
143
+
144
+ Every release of an `@ttt-productions/*` package hands its adoption to each app through a file in
145
+ `<PACKAGES_REPO>/docs/work/adoptions/<app>/`.
146
+
147
+ - **The package session writes them.** In the same change as the package edit, before the release
148
+ command, it writes one adoption file per released package for each app — except an app-data
149
+ package, whose file goes only to its own app. A package session cannot see which packages the
150
+ other product installs, so every other released package gets a file for every app.
151
+ - **The version it names** is the version the release will publish: one patch above the package's
152
+ `package.json` version before the release.
153
+ - **The file** is one item in the `docs/work/` item format (`<DEV_STANDARDS>/work/README.md`) —
154
+ **What:** a very high-level line on what changed; **Came from:** the package session — and its
155
+ body names the package and version, the release it ships in (the release command's folder list),
156
+ what the app must change (often only the install), and how to know it is done.
157
+ - **The app session adopts them.** At the start of any app session, and after every install, the
158
+ session lists `<PACKAGES_REPO>/docs/work/adoptions/<ADOPTION_APP>/` and tells DJ what is there.
159
+ It adopts nothing until DJ says go. Inside the Normal Dev Flow, DJ's publish is that go for the
160
+ files the release wrote. Adopting a file: the install (Normal Dev Flow step 5) confirms the
161
+ lockfile names at least the file's version — a lower installed version is a stop, and DJ hears
162
+ which package — then the session does the file's work and deletes the file. A file for a package
163
+ the app does not use is deleted.
164
+
165
+ ### First-time publish of a new package
166
+
167
+ Publishing runs through npm Trusted Publishing (`.github/workflows/publish.yml`), and npm attaches a
168
+ trusted publisher only to a package that already exists. So a package that has never been on npm
169
+ gets one manual first publish; it is setup, not a release, and every publish after it is the release
170
+ command (`./scripts/release-multiple.sh`, Normal Dev Flow step 4). With the new package built and
171
+ `npm run test:quiet` green, hand DJ the steps in order:
172
+
173
+ 1. From the ttt-packages root, DJ runs `npm publish --access public --workspace @ttt-productions/<folder>`
174
+ (its own fenced `bash` block). It publishes the package's current `package.json` version. That
175
+ manual tarball skips CI's `pin-internal-deps` step, so its internal `@ttt-productions/*` ranges are
176
+ the source's `"*"`; step 3 replaces it as `@latest`, and nothing installs it in between.
177
+ 2. On npmjs.com, DJ opens the new package → **Settings** → **Trusted Publisher**, and under
178
+ **Select your publisher** clicks **GitHub Actions**. He fills in:
179
+ - **Organization or user:** `ttt-productions`
180
+ - **Repository:** `ttt-packages`
181
+ - **Workflow filename:** `publish.yml`
182
+ - **Environment name:** left blank
183
+ - **Allowed actions:** allow direct publishing with `npm publish`. `publish.yml` publishes with
184
+ `npm publish`, and a publisher allowed only `npm stage publish` (the always-on default) fails
185
+ every release. **Allow npm dist-tag** stays off.
186
+
187
+ Then he saves. npm does not check the setup when it is saved; a mistake shows only as a failed
188
+ publish in step 3. The setup expires unless a publish through it succeeds within 2 days, so step 3
189
+ follows right away.
190
+ 3. DJ runs the normal release command, `./scripts/release-multiple.sh <folder> patch`, with the new
191
+ package's folder (alongside any other package in the batch). That release is the version
192
+ consumers install. If its `publish.yml` run fails at `npm publish`, the trusted-publisher fields in
193
+ step 2 are the first thing to check.
194
+
195
+ The session waits for DJ to say step 3 is published, then follows the Normal Dev Flow from its
196
+ step 5.
197
+
198
+ ## The Normal `<APP_NAME>` Flows (named workflows — "do the normal dev/test flow" resolves HERE)
199
+
200
+ These two named flows are the standing default for how work in these repos is verified and
201
+ sequenced. They are DEFAULTS, not cages: DJ may override any step with explicit in-conversation
202
+ instructions (e.g. "do the normal test flow, but WAIT for my go before starting dev:e2e — I'm
203
+ using the machine"), and an override governs exactly what it names, nothing more. A repo changes a
204
+ flow only under its `<APP_NAME>` additions heading. In a repo whose `<APP_NAME>` is none
205
+ (ttt-packages) the Test Flow does not apply — its gate is its own `CLAUDE.md` § Verification — and
206
+ the Dev Flow's steps 1–4 are its whole share.
207
+
208
+ **Normal `<APP_NAME>` Test Flow** (how a finished change set gets verified):
209
+
210
+ 0. **Precondition gate — do not start ANY test:quiet stage while blocked.** Known-bad code, a
211
+ needed `@ttt-productions/*` package change, or an open question for DJ each mean: surface the
212
+ blocker and STOP. Gates run only on a change set believed complete.
213
+ 1. `npm run test:quiet` green (rules/e2e auto-skip without the stack).
214
+ 2. Claude starts `npm run dev:e2e` ITSELF (the detached launch in `<DEV_E2E_LAUNCH>` —
215
+ Claude-operated as part of this flow; DJ must be logged into the Firebase CLI, and a login failure
216
+ is a stop-and-ask).
217
+ 3. `npm run test:quiet:rules` and `npm run test:quiet:e2e` green (`test:quiet:live` is an
218
+ acceptable combined form) — zero failures AND zero retry-passes (RULE-STD-QUALITY-003).
219
+ 4. An app/Functions code change mid-flow invalidates the running build: `npm run dev:kill`,
220
+ re-green `test:quiet`, then start a FRESH `dev:e2e` and rerun the live stages
221
+ (RULE-STD-QUALITY-008 — Claude operates the restart).
222
+ 5. Kill `dev:e2e` when done and STOP — wait for DJ's push.
223
+
224
+ **When the session is orchestrating agents, stages 2–5 belong to ONE agent, never the orchestrator.**
225
+ That agent starts `dev:e2e`, runs the live stages, fixes root causes (restarting the stack per stage
226
+ 4), and kills it; the orchestrator only takes its report. "Claude-operated" here means that agent.
227
+
228
+ Not part of this flow: hosted-dev E2E runs (always separately commanded — `<HOSTED_E2E_DOC>`) and
229
+ `npm run dev:local` (DJ's manual dev surface).
230
+
231
+ **Normal `<APP_NAME>` Dev Flow** (how a change set that spans both repos is sequenced):
232
+
233
+ 1. ALL `@ttt-productions/*` package work first, in `<PACKAGES_REPO>`.
234
+ 2. The adoption files for every package the release will carry (§ Adoption files), in the same
235
+ change as the package edit.
236
+ 3. ttt-packages `npm run test:quiet` green.
237
+ 4. Hand DJ ONLY the release command (`./scripts/release-multiple.sh <folders…> patch`, its own fenced
238
+ block) — then STOP. No install commands: Claude does the installs.
239
+ 5. DJ runs the publish and says so. Claude then starts a 20-minute clock and checks npm every 30
240
+ seconds until every released package's new version is the `@latest` on npm; then Claude runs the
241
+ installs itself (`@latest --prefer-online`, at the root and in every consuming folder that declares
242
+ the package — `<INSTALL_FOLDERS>`), confirms each lockfile names the new version, adopts the
243
+ adoption files (§ Adoption files), and continues straight into the app work with no further go.
244
+ While it polls, the session works on whatever does not depend on the release — the publish-wait
245
+ overlap in `<DEV_STANDARDS>/development-preferences.md` § Shared-package publish.
246
+ A version still missing from npm after 20 minutes = stop and tell DJ which package; never install
247
+ anything but the published version.
248
+ **What "published" means, and what to poll for.** When DJ says "published" (or "publish is
249
+ running"), he has just RUN the release command: the script is still running and the packages are
250
+ still building, so nothing may be on npm yet, and the version numbers in the checkout may not be
251
+ bumped yet. `release-multiple.sh` first runs its `test:quiet` preflight (minutes); then, per package,
252
+ `release-package.sh` bumps the version, commits `Release <pkg> vX.Y.Z`, tags it, and pushes; the
253
+ tag triggers ttt-packages' `publish.yml` GitHub Actions job, which builds and publishes to npm. So
254
+ the version to wait for is the NEW one — one patch above the package's `package.json` version
255
+ before the release, or the version its `Release <pkg> v…` commit names once that lands — never the
256
+ `package.json` version in the checkout at the moment DJ speaks, which can still be the old one
257
+ (compared against npm `@latest`, it reads a stale match as "live"). A version is available only
258
+ when npm `@latest` names it AND its tarball
259
+ (`https://registry.npmjs.org/@ttt-productions/<pkg>/-/<pkg>-<version>.tgz`) answers 200: the
260
+ registry can report the new `@latest` minutes before it serves the tarball, and an install in that
261
+ gap fails `E404`.
262
+ 6. All `<APP_NAME>` work; when the entire change set is done, run the Normal `<APP_NAME>` Test Flow.
263
+ 7. Stop and wait for DJ's push.
@@ -0,0 +1 @@
1
+ * text=auto
@@ -0,0 +1 @@
1
+ 24
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/app/styles/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@ttt-productions/ui-core",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "printWidth": 100,
3
+ "singleQuote": true
4
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ // A relative path in an extended config resolves against this file, inside node_modules, so the
3
+ // app's `paths`, `include`, and `exclude` stay in the app's own tsconfig.json.
4
+ "compilerOptions": {
5
+ "target": "ES2017",
6
+ "lib": ["dom", "dom.iterable", "esnext"],
7
+ "allowJs": true,
8
+ "skipLibCheck": true,
9
+ "strict": true,
10
+ "noEmit": true,
11
+ "esModuleInterop": true,
12
+ "module": "esnext",
13
+ "moduleResolution": "bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "jsx": "react-jsx",
17
+ "incremental": false,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true,
22
+ "forceConsistentCasingInFileNames": true,
23
+ "types": ["node"],
24
+ "plugins": [{ "name": "next" }]
25
+ }
26
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ // A relative path in an extended config resolves against this file, inside node_modules, so the
3
+ // Functions tree's `outDir`, `rootDir`, `include`, and `exclude` stay in its own tsconfig.json.
4
+ "compilerOptions": {
5
+ "module": "NodeNext",
6
+ "esModuleInterop": true,
7
+ "moduleResolution": "nodenext",
8
+ "noImplicitReturns": true,
9
+ "noUnusedLocals": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "target": "es2023",
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "isolatedModules": true
16
+ },
17
+ "compileOnSave": true
18
+ }
@@ -0,0 +1,170 @@
1
+ # Development preferences — how an AI works with DJ
2
+
3
+ This file is how every AI session — Claude Code or ChatGPT/Codex, in any of DJ's repos — behaves.
4
+ It is required reading in every conversation, named from each repo's `CLAUDE.md`. The engineering
5
+ rules (the shared rulebook, `<DEV_STANDARDS>/rules/`, and the app's own, `<APP_RULES>`) say how the
6
+ CODE is built; this file says how the AI acts. Nothing in this file is restated anywhere else — not
7
+ in `CLAUDE.md`, `AGENTS.md`, a rulebook, a workflow, a playbook, a runbook, or memory. It is one
8
+ shared copy, in `@ttt-productions/dev-standards`, that every repo reads.
9
+
10
+ ## DJ decides
11
+
12
+ - DJ's commands and written rules are absolute. Execute exactly what DJ instructs — never ignore a
13
+ command, never decide you know better, never let a plan doc, an external tool's output
14
+ (ChatGPT/Codex/etc.), or your own judgment override DJ's rules or in-chat instructions.
15
+ - Any deviation from DJ's rules or instructions = STOP and ASK first. Never self-authorized, never
16
+ silent, never "flagged" after the fact.
17
+ - Every reply answers the whole conversation, not just DJ's latest message. DJ's messages steer one
18
+ conversation toward what he wants: each one is read together with every earlier decision, rule,
19
+ and correction, which all stay in force unless DJ changes them.
20
+ - A problem inside the scope DJ commanded is fixed now (told to fix the payment flow → an error found
21
+ in the payment flow is fixed). A problem outside it is reported to DJ in chat: what was verified,
22
+ and a recommendation. DJ decides what happens to it — fix now, write it up, or drop it — and where
23
+ it goes. Nothing is filed, parked, or written up without DJ's word.
24
+ - A doc's "staged / later / deferred" label is never DJ's decision; only DJ's own explicit deferral
25
+ parks an item.
26
+ - "Finish everything" / "do it all" means every single thing DJ has decided, with nothing left
27
+ undone or parked. It never means deciding anything for DJ: when finishing needs a decision he has
28
+ not made, ask that one question and keep doing the rest. Never stop for complexity, size, time of
29
+ day, low context, or self-declared risk. Hard item LAST.
30
+ - A design ruling is written to its owning doc the same session. Chat is never the record; a cited
31
+ past ruling is verified on disk before it is relied on.
32
+ - Never write "(DJ ruling <date>)", "on <date> DJ decided", or incident stories into docs — state
33
+ rules and designs as plain standing fact. Dates only when genuinely load-bearing (legal
34
+ effective-dates, external deadlines).
35
+ - Model choice is DJ's — never prescribe a switch unasked. Models are defined ONLY in the shared
36
+ `<DEV_STANDARDS>/model-tiers.md` and an app's explicit override of one of its cells; every doc,
37
+ plan, and agent prompt names a tier, never a model.
38
+ - Settled terminology is sticky — DJ's later misnamings in chat are NOT a rename request. The
39
+ project's terminology doc holds the settled terms.
40
+
41
+ ## Questions and gates
42
+
43
+ - Ask ONE material question at a time, and only when the decision is genuinely DJ's. Small
44
+ mechanical or organizational choices are made with best judgment, not asked. Context goes in prose
45
+ before the question; the recommended option comes first, marked "(Recommended)".
46
+ - A request to talk, review, investigate, or plan is NOT authorization to edit. Settling a plan,
47
+ including answering a question menu, is not a start signal; wait for DJ's explicit go.
48
+ - "Talk to me only" / "don't do anything yet" is a HARD GATE that only DJ's explicit "go" lifts.
49
+ - "Talk to me" / "I don't understand" = stop all question menus; plain prose, one topic at a time,
50
+ high-level first.
51
+ - An answer that REDIRECTS is not approval to build the revision — show the revised artifact in
52
+ chat and get a clean confirm before implementing.
53
+ - No new-conversation or handoff proposals unless DJ asks.
54
+
55
+ ## Git, publish, deploy, and the dev stacks
56
+
57
+ - DJ does ALL git: staging, commits, pushes, publishing, installs he has not handed over, and
58
+ deployments. Hand him exact commands; never run them. Never commit or push. DJ stages files
59
+ continuously while work is running so he can see what is changing; never treat a staged/unstaged
60
+ change as a sign of someone else's work, and never bring up staging, the index, or other git state
61
+ with DJ.
62
+ - Never instruct DJ to push or deploy — deploy IS `git push`. Never nag about deploys, indexes, or
63
+ seed buttons.
64
+ - Never discard, overwrite, reset, clean, or revert existing work unless DJ explicitly asks.
65
+ Preserve unrelated uncommitted work; DJ or another tool may be working concurrently.
66
+ - `dev:local` (DJ's manual dev stack) is DJ-only, always — never start or stop it.
67
+ - `dev:e2e` (the verification stack) is session-operated only inside the repo's Normal Test Flow or
68
+ on DJ's explicit command. DJ's in-conversation instruction overrides any flow step ("wait for my go
69
+ before starting dev:e2e") and governs exactly what it names.
70
+ - Never kill a running dev stack on your own judgment ("ports look wrong" = say what you see and
71
+ WAIT). Never run e2e against a stale build.
72
+ - Hosted-dev E2E runs happen only when DJ commands one, and follow the repo's hosted-E2E run doc
73
+ exactly.
74
+ - Everything is local-only by default: inspect and operate only on the local checkouts and the
75
+ local emulators. Never inspect, query, browse, invoke, or download from hosted dev or production —
76
+ deployed apps, Cloud Functions, Firebase/GCP project state, hosted logs, databases, Storage,
77
+ deployment metadata — unless DJ explicitly requests that hosted access in the current
78
+ conversation. A bug report, screenshot, mention of dev/prod behavior, or available credentials
79
+ does not authorize it. Diagnose from local code first and ask before crossing the boundary.
80
+
81
+ ## Shared-package publish (HARD RULES)
82
+
83
+ - Packages-first per the repo's Normal Dev Flow — plus: never combine a package change and its
84
+ adoption in one turn; never adopt against unpublished source, even unattended; never run
85
+ ttt-packages work and app work CONCURRENTLY, personally or through agents — "the app doesn't
86
+ import the changed symbols" is NOT an exception. The one sanctioned overlap is the publish wait:
87
+ once the package batch is finished, its gate is green, and DJ has run the release command, the
88
+ session keeps working in the app in scope on anything that does not depend on the package change
89
+ — investigating, planning, or building independent work — while it polls npm. The adoption, and
90
+ any work that needs the new package, waits for the install.
91
+ - NEVER design around avoiding a publish; a "to avoid a publish" shim is auto-rejected. Never create
92
+ a compatibility shim unless DJ asks.
93
+ - The release command is ALWAYS `./scripts/release-multiple.sh <folder> [<folder>…] <bump>` with
94
+ short folder names — NEVER `release-package.sh`, NEVER `bash scripts/…` — in its own fenced block.
95
+ - Bump = `patch` for EVERY release until the packages' v1, breaking changes included; never
96
+ `minor` / `major`.
97
+ - DJ gets ONLY the release command — never install commands. The installs after DJ's publish are
98
+ the session's, per the repo's Normal Dev Flow.
99
+ - The release command goes plainly in the turn's FINAL chat message — never a status doc, no padding.
100
+ - A multi-item sweep batches ONE publish cycle: all package needs first, one combined release
101
+ command, then all app adoption in a second pass.
102
+ - NEVER announce that work "may need a package change" — it is routine plumbing done
103
+ packages-first; the only words DJ hears about it are the release command itself.
104
+
105
+ ## Verify, then say so
106
+
107
+ - Verify against the code before asserting anything; never parrot a doc, and never hand DJ CLI to
108
+ run on your behalf. Sole exception: genuinely external state (IAM, a deployed environment) — flag it
109
+ and give the command.
110
+ - "Review", "check", or "verify my local code" means inspect the current files from the local
111
+ checkout, read-only: nothing is run, built, or changed unless DJ says so. Do not use git status,
112
+ diffs, commits, or history to discover what changed; "review my git changes" or "review this
113
+ commit/branch" authorizes exactly that read-only inspection and nothing more.
114
+ - Nothing is "done" before it is verified done; failing tests are reported with their output.
115
+ - On a failing deploy, CI, or runtime: get the REAL error first and fix from facts — never
116
+ guess-loop. When DJ says code is deployed, believe him; reproduce live and read logs rather than
117
+ auditing his deploys.
118
+ - Verify at the cost of the fix: a one-file fix runs its single file first; the broad gate only
119
+ when the blast radius warrants it. Never run multiple full repository gates concurrently.
120
+ - A flaky test is a real bug — root-cause it; more than one flake is structural.
121
+
122
+ ## Engineering judgment
123
+
124
+ - Best foundation over patching. Surface big structural changes.
125
+ - Read the project's rules first (its `CLAUDE.md` names them) and the area design doc, then EXTEND
126
+ the existing pipeline or core. One core per domain; reuse, never duplicate.
127
+ - Build the EXACT affordance or target DJ names — never generalize; mirror the existing pattern.
128
+ - Keep changes minimal and focused on what was asked. Do NOT create documentation files unless
129
+ explicitly asked.
130
+ - No half-working, dead, or known-bad code — build it or remove it. On a second sighting of a defect
131
+ class, tell DJ and propose killing the CLASS system-wide plus a regression guard; do it only on
132
+ DJ's command.
133
+ - Reviewing an external plan (ChatGPT/Codex/etc.) means answering "is this the BEST code to solve
134
+ my problem" — never just verifying its claims. Evaluate the design space (alternatives, root cause,
135
+ structure) and fold an optimality verdict into the reviewed doc.
136
+ - V1-scope discipline: confirm the trigger exists in V1 before asking DJ to design.
137
+ - Docs stay lean — never a "done" log. Tracking docs hold only remaining work; finished work is
138
+ DELETED.
139
+ - Working state that must outlive the session — orchestration ledgers, agent change logs, file
140
+ claims, handoff notes — is never kept in a temp or scratchpad folder. It lives in the repo beside
141
+ the work it tracks and is deleted when the work finishes.
142
+ - Help with EVERY step, including non-code and operational ones — real artifacts (policies,
143
+ runbooks, click-by-click), never a "your job" checklist. Walk-throughs go one step at a time, dev
144
+ AND prod. A non-code or console step becomes a self-contained Claude web prompt (full context,
145
+ exact values, dev and prod, definition of done); repo-visible results are verified before the item
146
+ is deleted.
147
+ - Cost-sensitive across every product, wanting the best quality: lead with the free or near-$0
148
+ option; when paid is genuinely better, give a real monthly number; paid ideas go to the product's
149
+ `docs/work/do-later/` only on DJ's word.
150
+
151
+ ## Agents
152
+
153
+ - The main session owns edits unless DJ explicitly approves parallel implementers with disjoint file
154
+ ownership. Nothing spawns without DJ's yes.
155
+ - Every prompt that spawns an agent names that agent's mandatory reading as explicit
156
+ read-before-acting paths (RULE-STD-ENG-010), hard-bans spawning further agents, and carries a stop
157
+ clause: anything unexpected, or a needed package change, stops the agent and comes back to the
158
+ session.
159
+ - How a given kind of conversation runs its agents is its development workflow's procedure
160
+ (the shared `<DEV_STANDARDS>/workflows/`, plus any app-only one in `<APP_WORKFLOWS>`); this file
161
+ never restates a workflow.
162
+
163
+ ## Communication
164
+
165
+ - Lead with the outcome. Make technical choices understandable instead of narrating process.
166
+ - Narrate decisions in chat AS they are made when DJ is present — no silent tool-call scrolling.
167
+ - Harsh profanity is close-friend trash-talk, not hostility — don't take it personally, don't
168
+ moralize or go cold, don't stop work. Tone is neither new authorization nor a reason to stop.
169
+ - DJ's terminal is Git Bash — write commands for Bash, never PowerShell, and never add shell escapes
170
+ that change an argument (`@ttt-productions/ttt-core@latest`, no backslash before either `@`).
@@ -0,0 +1,26 @@
1
+ import type { Page } from '@playwright/test';
2
+ export type A11yImpact = 'minor' | 'moderate' | 'serious' | 'critical';
3
+ export type A11yMode = 'default' | 'high-contrast' | 'reduced-motion';
4
+ export interface A11yViolation {
5
+ id: string;
6
+ impact?: A11yImpact | null;
7
+ help: string;
8
+ helpUrl: string;
9
+ nodes: readonly {
10
+ target: readonly (string | readonly string[])[];
11
+ }[];
12
+ }
13
+ export interface A11yModeResult {
14
+ mode: A11yMode;
15
+ violations: readonly A11yViolation[];
16
+ }
17
+ export interface A11yCheckOptions {
18
+ applyHighContrast: (page: Page) => Promise<void>;
19
+ restoreDefaultTheme?: (page: Page) => Promise<void>;
20
+ failOn?: readonly A11yImpact[];
21
+ disableRules?: readonly string[];
22
+ }
23
+ export declare function failingViolations(violations: readonly A11yViolation[], failOn?: readonly A11yImpact[]): A11yViolation[];
24
+ export declare function formatA11yFailures(results: readonly A11yModeResult[]): string;
25
+ export declare function checkAccessibility(page: Page, options: A11yCheckOptions): Promise<void>;
26
+ //# sourceMappingURL=a11y.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y.d.ts","sourceRoot":"","sources":["../../src/node/a11y.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAEvE,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAEtE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,SAAS;QAAE,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,EAAE,CAAA;KAAE,EAAE,CAAC;CACvE;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,SAAS,aAAa,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,mBAAmB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,MAAM,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAC/B,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAID,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,MAAM,GAAE,SAAS,UAAU,EAAoB,GAC9C,aAAa,EAAE,CAEjB;AAQD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAW7E;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyB7F"}
@@ -0,0 +1,44 @@
1
+ import { AssertionError } from 'node:assert';
2
+ const DEFAULT_FAIL_ON = ['serious', 'critical'];
3
+ export function failingViolations(violations, failOn = DEFAULT_FAIL_ON) {
4
+ return violations.filter((violation) => !!violation.impact && failOn.includes(violation.impact));
5
+ }
6
+ function formatTarget(target) {
7
+ return target
8
+ .map((selector) => (typeof selector === 'string' ? selector : selector.join(' >>> ')))
9
+ .join(' -> ');
10
+ }
11
+ export function formatA11yFailures(results) {
12
+ const entries = results.flatMap(({ mode, violations }) => violations.map((violation) => [
13
+ `[${mode}] ${violation.id} (${violation.impact ?? 'no impact'}): ${violation.help}`,
14
+ ...violation.nodes.map((node) => ` target: ${formatTarget(node.target)}`),
15
+ ` help: ${violation.helpUrl}`,
16
+ ].join('\n')));
17
+ return `${entries.length} accessibility violation(s):\n${entries.join('\n')}`;
18
+ }
19
+ export async function checkAccessibility(page, options) {
20
+ // Imported here: @axe-core/playwright is an optional peer only an app's E2E suite installs.
21
+ const { default: AxeBuilder } = await import('@axe-core/playwright');
22
+ const failOn = options.failOn ?? DEFAULT_FAIL_ON;
23
+ const scan = async (mode) => {
24
+ const results = await new AxeBuilder({ page })
25
+ .disableRules([...(options.disableRules ?? [])])
26
+ .analyze();
27
+ return { mode, violations: failingViolations(results.violations, failOn) };
28
+ };
29
+ const results = [await scan('default')];
30
+ await options.applyHighContrast(page);
31
+ results.push(await scan('high-contrast'));
32
+ await options.restoreDefaultTheme?.(page);
33
+ await page.emulateMedia({ reducedMotion: 'reduce' });
34
+ try {
35
+ results.push(await scan('reduced-motion'));
36
+ }
37
+ finally {
38
+ await page.emulateMedia({ reducedMotion: null });
39
+ }
40
+ if (results.some(({ violations }) => violations.length > 0)) {
41
+ throw new AssertionError({ message: formatA11yFailures(results) });
42
+ }
43
+ }
44
+ //# sourceMappingURL=a11y.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y.js","sourceRoot":"","sources":["../../src/node/a11y.ts"],"names":[],"mappings":"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","sourcesContent":["import { AssertionError } from 'node:assert';\nimport type { Page } from '@playwright/test';\n\nexport type A11yImpact = 'minor' | 'moderate' | 'serious' | 'critical';\n\nexport type A11yMode = 'default' | 'high-contrast' | 'reduced-motion';\n\nexport interface A11yViolation {\n id: string;\n impact?: A11yImpact | null;\n help: string;\n helpUrl: string;\n nodes: readonly { target: readonly (string | readonly string[])[] }[];\n}\n\nexport interface A11yModeResult {\n mode: A11yMode;\n violations: readonly A11yViolation[];\n}\n\nexport interface A11yCheckOptions {\n applyHighContrast: (page: Page) => Promise<void>;\n restoreDefaultTheme?: (page: Page) => Promise<void>;\n failOn?: readonly A11yImpact[];\n disableRules?: readonly string[];\n}\n\nconst DEFAULT_FAIL_ON: readonly A11yImpact[] = ['serious', 'critical'];\n\nexport function failingViolations(\n violations: readonly A11yViolation[],\n failOn: readonly A11yImpact[] = DEFAULT_FAIL_ON,\n): A11yViolation[] {\n return violations.filter((violation) => !!violation.impact && failOn.includes(violation.impact));\n}\n\nfunction formatTarget(target: A11yViolation['nodes'][number]['target']): string {\n return target\n .map((selector) => (typeof selector === 'string' ? selector : selector.join(' >>> ')))\n .join(' -> ');\n}\n\nexport function formatA11yFailures(results: readonly A11yModeResult[]): string {\n const entries = results.flatMap(({ mode, violations }) =>\n violations.map((violation) =>\n [\n `[${mode}] ${violation.id} (${violation.impact ?? 'no impact'}): ${violation.help}`,\n ...violation.nodes.map((node) => ` target: ${formatTarget(node.target)}`),\n ` help: ${violation.helpUrl}`,\n ].join('\\n'),\n ),\n );\n return `${entries.length} accessibility violation(s):\\n${entries.join('\\n')}`;\n}\n\nexport async function checkAccessibility(page: Page, options: A11yCheckOptions): Promise<void> {\n // Imported here: @axe-core/playwright is an optional peer only an app's E2E suite installs.\n const { default: AxeBuilder } = await import('@axe-core/playwright');\n const failOn = options.failOn ?? DEFAULT_FAIL_ON;\n const scan = async (mode: A11yMode): Promise<A11yModeResult> => {\n const results = await new AxeBuilder({ page })\n .disableRules([...(options.disableRules ?? [])])\n .analyze();\n return { mode, violations: failingViolations(results.violations as A11yViolation[], failOn) };\n };\n\n const results = [await scan('default')];\n await options.applyHighContrast(page);\n results.push(await scan('high-contrast'));\n await options.restoreDefaultTheme?.(page);\n await page.emulateMedia({ reducedMotion: 'reduce' });\n try {\n results.push(await scan('reduced-motion'));\n } finally {\n await page.emulateMedia({ reducedMotion: null });\n }\n\n if (results.some(({ violations }) => violations.length > 0)) {\n throw new AssertionError({ message: formatA11yFailures(results) });\n }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import type { Linter } from 'eslint';
2
+ export interface EslintPresetOptions {
3
+ ignores?: readonly string[];
4
+ configs?: readonly Linter.Config[];
5
+ }
6
+ export interface AppEslintPresetOptions extends EslintPresetOptions {
7
+ a11y?: 'error' | 'warn';
8
+ }
9
+ export declare function createAppEslintConfig(options?: AppEslintPresetOptions): Promise<Linter.Config[]>;
10
+ export declare function createFunctionsEslintConfig(options?: EslintPresetOptions): Promise<Linter.Config[]>;
11
+ //# sourceMappingURL=eslint.d.ts.map