create-backbone-template 0.1.0
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.
- package/README.md +33 -0
- package/bin/create-backbone-template.js +5 -0
- package/package.json +30 -0
- package/src/create-backbone-template.js +204 -0
- package/template/.agents/skills/agent-browser/SKILL.md +55 -0
- package/template/.agents/skills/create-plan/SKILL.md +52 -0
- package/template/.agents/skills/create-plan/agents/openai.yaml +4 -0
- package/template/.agents/skills/create-pr-presentation/SKILL.md +86 -0
- package/template/.agents/skills/create-pr-presentation/agents/openai.yaml +4 -0
- package/template/.agents/skills/implement-plan/SKILL.md +26 -0
- package/template/.agents/skills/implement-plan/agents/openai.yaml +4 -0
- package/template/.agents/skills/review-plan/SKILL.md +38 -0
- package/template/.agents/skills/review-plan/agents/openai.yaml +4 -0
- package/template/.env.schema +30 -0
- package/template/.env.test +6 -0
- package/template/.oxlintrc.json +67 -0
- package/template/.vscode/extensions.json +3 -0
- package/template/.vscode/settings.json +23 -0
- package/template/AGENTS.md +55 -0
- package/template/Cargo.lock +2648 -0
- package/template/Cargo.toml +29 -0
- package/template/Justfile +140 -0
- package/template/README.md +72 -0
- package/template/TODO.md +1 -0
- package/template/_gitignore +12 -0
- package/template/buf.gen.yaml +7 -0
- package/template/buf.yaml +10 -0
- package/template/client/.oxfmtrc.json +8 -0
- package/template/client/.oxlintrc.json +57 -0
- package/template/client/README.md +19 -0
- package/template/client/_gitignore +5 -0
- package/template/client/index.html +12 -0
- package/template/client/package.json +47 -0
- package/template/client/packages/design-system/package.json +19 -0
- package/template/client/packages/design-system/src/index.ts +2 -0
- package/template/client/packages/design-system-basic/package.json +18 -0
- package/template/client/packages/design-system-basic/src/button.stories.tsx +50 -0
- package/template/client/packages/design-system-basic/src/button.tsx +26 -0
- package/template/client/packages/design-system-basic/src/empty-state.stories.tsx +18 -0
- package/template/client/packages/design-system-basic/src/empty-state.tsx +17 -0
- package/template/client/packages/design-system-basic/src/form-field.stories.tsx +15 -0
- package/template/client/packages/design-system-basic/src/form-field.tsx +10 -0
- package/template/client/packages/design-system-basic/src/form.stories.tsx +27 -0
- package/template/client/packages/design-system-basic/src/form.tsx +9 -0
- package/template/client/packages/design-system-basic/src/heading.stories.tsx +14 -0
- package/template/client/packages/design-system-basic/src/heading.tsx +25 -0
- package/template/client/packages/design-system-basic/src/index.tsx +15 -0
- package/template/client/packages/design-system-basic/src/inline.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/inline.tsx +5 -0
- package/template/client/packages/design-system-basic/src/layout.stories.tsx +24 -0
- package/template/client/packages/design-system-basic/src/layout.tsx +14 -0
- package/template/client/packages/design-system-basic/src/loader.stories.tsx +8 -0
- package/template/client/packages/design-system-basic/src/loader.tsx +11 -0
- package/template/client/packages/design-system-basic/src/navigation.stories.tsx +16 -0
- package/template/client/packages/design-system-basic/src/navigation.tsx +18 -0
- package/template/client/packages/design-system-basic/src/notice.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/notice.tsx +5 -0
- package/template/client/packages/design-system-basic/src/stack.stories.tsx +17 -0
- package/template/client/packages/design-system-basic/src/stack.tsx +5 -0
- package/template/client/packages/design-system-basic/src/styles.css +254 -0
- package/template/client/packages/design-system-basic/src/text-input.stories.tsx +13 -0
- package/template/client/packages/design-system-basic/src/text-input.tsx +5 -0
- package/template/client/packages/design-system-basic/src/text.stories.tsx +21 -0
- package/template/client/packages/design-system-basic/src/text.tsx +5 -0
- package/template/client/packages/design-system-contract/package.json +15 -0
- package/template/client/packages/design-system-contract/src/button.ts +10 -0
- package/template/client/packages/design-system-contract/src/empty-state.ts +9 -0
- package/template/client/packages/design-system-contract/src/form-field.ts +9 -0
- package/template/client/packages/design-system-contract/src/form.ts +9 -0
- package/template/client/packages/design-system-contract/src/heading.ts +9 -0
- package/template/client/packages/design-system-contract/src/index.ts +13 -0
- package/template/client/packages/design-system-contract/src/inline.ts +7 -0
- package/template/client/packages/design-system-contract/src/layout.ts +8 -0
- package/template/client/packages/design-system-contract/src/loader.ts +7 -0
- package/template/client/packages/design-system-contract/src/navigation.ts +13 -0
- package/template/client/packages/design-system-contract/src/notice.ts +8 -0
- package/template/client/packages/design-system-contract/src/stack.ts +8 -0
- package/template/client/packages/design-system-contract/src/text-input.ts +5 -0
- package/template/client/packages/design-system-contract/src/text.ts +9 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/external-ui-import.tsx +5 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/raw-dom-jsx.tsx +3 -0
- package/template/client/packages/design-system-lint/fixtures/invalid/two-violations.tsx +7 -0
- package/template/client/packages/design-system-lint/fixtures/valid/design-system-only.tsx +13 -0
- package/template/client/packages/design-system-lint/package.json +23 -0
- package/template/client/packages/design-system-lint/src/check-design-system-architecture.ts +22 -0
- package/template/client/packages/design-system-lint/src/design-system-architecture.ts +286 -0
- package/template/client/packages/design-system-lint/src/oxlint-plugin.ts +11 -0
- package/template/client/packages/design-system-lint/src/page-architecture.ts +382 -0
- package/template/client/packages/design-system-lint/src/rules.ts +111 -0
- package/template/client/packages/design-system-lint/test/design-system-architecture.test.ts +243 -0
- package/template/client/packages/design-system-lint/test/oxlint-fixtures.test.ts +159 -0
- package/template/client/packages/design-system-lint/test/page-architecture.test.ts +175 -0
- package/template/client/packages/design-system-lint/test/rules.test.ts +65 -0
- package/template/client/packages/design-system-lint/tsconfig.json +29 -0
- package/template/client/src/App.tsx +77 -0
- package/template/client/src/design-system-components.test.tsx +75 -0
- package/template/client/src/gen/helloworld/v1/helloworld_pb.ts +63 -0
- package/template/client/src/main.tsx +18 -0
- package/template/client/src/pages/hello/hello-page.stories.tsx +20 -0
- package/template/client/src/pages/hello/hello-page.test.tsx +90 -0
- package/template/client/src/pages/hello/hello-page.tsx +126 -0
- package/template/client/src/pages/page.ts +20 -0
- package/template/client/src/testing/create-preview-events.test.ts +36 -0
- package/template/client/src/testing/create-preview-events.ts +30 -0
- package/template/client/src/vite-env.d.ts +1 -0
- package/template/client/tsconfig.json +32 -0
- package/template/client/vite.config.ts +21 -0
- package/template/client/vite.ladle.config.ts +5 -0
- package/template/e2e/.gherkin-lintrc +20 -0
- package/template/e2e/.oxfmtrc.json +15 -0
- package/template/e2e/.oxlintrc.json +37 -0
- package/template/e2e/_gitignore +4 -0
- package/template/e2e/features/helloworld.feature +10 -0
- package/template/e2e/package.json +42 -0
- package/template/e2e/playwright.config.ts +16 -0
- package/template/e2e/support/app-gherkin.ts +4 -0
- package/template/e2e/support/fixtures.ts +236 -0
- package/template/e2e/support/gherkin-fixtures/duplicate-id.feature +9 -0
- package/template/e2e/support/gherkin-fixtures/duplicate-id.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/extra-implementation.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/extra-step.spec.ts +10 -0
- package/template/e2e/support/gherkin-fixtures/happy-path.spec.ts +4 -0
- package/template/e2e/support/gherkin-fixtures/missing-id.feature +4 -0
- package/template/e2e/support/gherkin-fixtures/missing-id.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/missing-implementation.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/missing-step.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/playwright.config.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/scenario-outline.feature +9 -0
- package/template/e2e/support/gherkin-fixtures/scenario-outline.spec.ts +7 -0
- package/template/e2e/support/gherkin-fixtures/step-mismatch.spec.ts +9 -0
- package/template/e2e/support/gherkin-fixtures/valid-implementations.ts +23 -0
- package/template/e2e/support/gherkin-fixtures/valid-scenarios.feature +26 -0
- package/template/e2e/support/gherkin.test.ts +184 -0
- package/template/e2e/support/gherkin.ts +321 -0
- package/template/e2e/support/oxlint-plugin.test.ts +328 -0
- package/template/e2e/support/oxlint-plugin.ts +485 -0
- package/template/e2e/tests/helloworld.spec.ts +39 -0
- package/template/e2e/tsconfig.json +26 -0
- package/template/e2e/tsconfig.oxlint-plugin.json +12 -0
- package/template/package.json +9 -0
- package/template/pnpm-lock.yaml +10723 -0
- package/template/pnpm-workspace.yaml +8 -0
- package/template/pr-slide/README.md +95 -0
- package/template/pr-slide/package.json +23 -0
- package/template/pr-slide/src/cli.js +262 -0
- package/template/pr-slide/src/generate-pr-deck.js +833 -0
- package/template/pr-slide/src/git-context.js +91 -0
- package/template/pr-slide/src/presentation-paths.js +9 -0
- package/template/pr-slide/src/presentations.js +53 -0
- package/template/pr-slide/test/generate-pr-deck.test.js +118 -0
- package/template/pr-slide/test/presentation-paths.test.js +14 -0
- package/template/pr-slide/test/presentations.test.js +50 -0
- package/template/proto/helloworld/v1/helloworld.proto +15 -0
- package/template/scripts/run-e2e.sh +10 -0
- package/template/server/Cargo.toml +26 -0
- package/template/server/build.rs +9 -0
- package/template/server/dylint/backbone_server_lints/.cargo/config.toml +6 -0
- package/template/server/dylint/backbone_server_lints/Cargo.lock +1581 -0
- package/template/server/dylint/backbone_server_lints/Cargo.toml +21 -0
- package/template/server/dylint/backbone_server_lints/README.md +5 -0
- package/template/server/dylint/backbone_server_lints/_gitignore +1 -0
- package/template/server/dylint/backbone_server_lints/rust-toolchain +3 -0
- package/template/server/dylint/backbone_server_lints/src/lib.rs +612 -0
- package/template/server/dylint/backbone_server_lints/ui/lib.rs +4 -0
- package/template/server/dylint/backbone_server_lints/ui/lib.stderr +10 -0
- package/template/server/dylint/backbone_server_lints/ui/long_file.rs +303 -0
- package/template/server/dylint/backbone_server_lints/ui/long_file.stderr +6 -0
- package/template/server/dylint/backbone_server_lints/ui/main.rs +59 -0
- package/template/server/dylint/backbone_server_lints/ui/main.stderr +85 -0
- package/template/server/migrations/20260520120000_create_projects.sql +12 -0
- package/template/server/migrations/20260524160000_create_hello_world_inputs.sql +12 -0
- package/template/server/src/config.rs +27 -0
- package/template/server/src/db/hello_world.rs +34 -0
- package/template/server/src/db/hello_world_tests.rs +11 -0
- package/template/server/src/db/mod.rs +39 -0
- package/template/server/src/lib.rs +10 -0
- package/template/server/src/main.rs +43 -0
- package/template/server/src/rpc/greeter/mod.rs +31 -0
- package/template/server/src/rpc/greeter/say_hello.rs +27 -0
- package/template/server/src/rpc/mod.rs +8 -0
- package/template/server/src/state.rs +13 -0
- package/template/skills-lock.json +11 -0
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
const emptyCopy = {
|
|
2
|
+
pages: "No page-level UI changes detected yet.",
|
|
3
|
+
behaviors: "No e2e behavior changes detected yet.",
|
|
4
|
+
designSystem: "No design system changes detected yet.",
|
|
5
|
+
backend: "No backend implementation changes detected yet.",
|
|
6
|
+
database: "No database or migration changes detected yet.",
|
|
7
|
+
protos: "No proto contract changes detected yet.",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function buildDeck(context) {
|
|
11
|
+
const generatedAt = normalizeDate(context.generatedAt);
|
|
12
|
+
const sections = normalizeSections(context.sections);
|
|
13
|
+
const changedFiles = context.changedFiles ?? [];
|
|
14
|
+
const featureItems = featureItemsFromBehaviors(sections.behaviors);
|
|
15
|
+
|
|
16
|
+
return [
|
|
17
|
+
"---",
|
|
18
|
+
"title: " + yamlString(context.title),
|
|
19
|
+
"info: Generated by backbone pr-slide.",
|
|
20
|
+
"drawings:",
|
|
21
|
+
" persist: false",
|
|
22
|
+
"transition: slide-left",
|
|
23
|
+
"---",
|
|
24
|
+
"",
|
|
25
|
+
retroStyles(),
|
|
26
|
+
"",
|
|
27
|
+
`<section class="retro-stage title-stage">`,
|
|
28
|
+
`<div class="stamp">Prepared for the Review Committee</div>`,
|
|
29
|
+
`<div class="paperclip">📎</div>`,
|
|
30
|
+
`<div class="memo-window">`,
|
|
31
|
+
`<div class="win95-titlebar"><span>WINDOW 3.1-ish PR_BRIEF.EXE</span><span>□ ×</span></div>`,
|
|
32
|
+
`<div class="memo-body">`,
|
|
33
|
+
`<p class="kicker">Backbone corporate intranet transmission</p>`,
|
|
34
|
+
`<h1>${escapeHtml(context.title)}</h1>`,
|
|
35
|
+
`<p class="purpose">${escapeHtml(catchyPurpose(context.purpose))}</p>`,
|
|
36
|
+
`<div class="floppy-label">💾 ${escapeHtml(context.branch)} -> ${escapeHtml(context.base)} · ${generatedAt}</div>`,
|
|
37
|
+
`</div>`,
|
|
38
|
+
`</div>`,
|
|
39
|
+
`</section>`,
|
|
40
|
+
"",
|
|
41
|
+
"---",
|
|
42
|
+
"class: retro-slide",
|
|
43
|
+
"---",
|
|
44
|
+
"",
|
|
45
|
+
`<div class="retro-stage split-stage">`,
|
|
46
|
+
`<div>`,
|
|
47
|
+
`<p class="kicker">Quarterly roadmap energy</p>`,
|
|
48
|
+
`<h1>Pull Request Overview</h1>`,
|
|
49
|
+
`<p class="big-promise">${escapeHtml(catchyPurpose(context.purpose))}</p>`,
|
|
50
|
+
`</div>`,
|
|
51
|
+
`<div class="office-oracle">`,
|
|
52
|
+
`<div class="oracle-face">📎</div>`,
|
|
53
|
+
`<div class="speech-bubble">I see changed files. I also see feelings.</div>`,
|
|
54
|
+
`<div class="tiny-caption">CLIPPY-ADJACENT HELPER, LEGALLY DISTINCT</div>`,
|
|
55
|
+
`</div>`,
|
|
56
|
+
`</div>`,
|
|
57
|
+
"",
|
|
58
|
+
"---",
|
|
59
|
+
"class: retro-slide",
|
|
60
|
+
"---",
|
|
61
|
+
"",
|
|
62
|
+
"## User-Facing Page Changes",
|
|
63
|
+
"",
|
|
64
|
+
`<div class="slide-subtitle">What pages changed and what they help users accomplish.</div>`,
|
|
65
|
+
"",
|
|
66
|
+
formatWindowItems(sections.pages, emptyCopy.pages, {
|
|
67
|
+
icon: "🖥️",
|
|
68
|
+
sticker: "ROUTE",
|
|
69
|
+
emptyIcon: "📠",
|
|
70
|
+
}),
|
|
71
|
+
"",
|
|
72
|
+
"---",
|
|
73
|
+
"class: retro-slide",
|
|
74
|
+
"---",
|
|
75
|
+
"",
|
|
76
|
+
"## Features",
|
|
77
|
+
"",
|
|
78
|
+
`<div class="slide-subtitle">Product capabilities described by the end-to-end specifications.</div>`,
|
|
79
|
+
"",
|
|
80
|
+
formatFeatureOverview(featureItems, emptyCopy.behaviors),
|
|
81
|
+
"",
|
|
82
|
+
...formatFeatureSlides(featureItems),
|
|
83
|
+
"",
|
|
84
|
+
"---",
|
|
85
|
+
"class: retro-slide",
|
|
86
|
+
"---",
|
|
87
|
+
"",
|
|
88
|
+
"## Design System Updates",
|
|
89
|
+
"",
|
|
90
|
+
`<div class="slide-subtitle">Reusable UI components, stories, and interaction states updated by this PR.</div>`,
|
|
91
|
+
"",
|
|
92
|
+
formatStickerItems(sections.designSystem, emptyCopy.designSystem),
|
|
93
|
+
"",
|
|
94
|
+
"---",
|
|
95
|
+
"class: retro-slide",
|
|
96
|
+
"---",
|
|
97
|
+
"",
|
|
98
|
+
`<div class="retro-stage basement-title">`,
|
|
99
|
+
`<div class="warning-sign">⚠</div>`,
|
|
100
|
+
`<h1>Technical Implementation</h1>`,
|
|
101
|
+
`<p>Contracts, data, and backend services that support the user-facing changes.</p>`,
|
|
102
|
+
`</div>`,
|
|
103
|
+
"",
|
|
104
|
+
"---",
|
|
105
|
+
"class: retro-slide",
|
|
106
|
+
"---",
|
|
107
|
+
"",
|
|
108
|
+
"## API Contract Changes",
|
|
109
|
+
"",
|
|
110
|
+
formatApiOverview(sections.protos, emptyCopy.protos),
|
|
111
|
+
"",
|
|
112
|
+
...formatProtoServiceSlides(sections.protos),
|
|
113
|
+
"",
|
|
114
|
+
"---",
|
|
115
|
+
"class: retro-slide",
|
|
116
|
+
"---",
|
|
117
|
+
"",
|
|
118
|
+
"## Database And Persistence Changes",
|
|
119
|
+
"",
|
|
120
|
+
formatDatabaseOverview(sections.database, emptyCopy.database),
|
|
121
|
+
"",
|
|
122
|
+
...formatMigrationSlides(sections.database),
|
|
123
|
+
"",
|
|
124
|
+
"---",
|
|
125
|
+
"class: retro-slide",
|
|
126
|
+
"---",
|
|
127
|
+
"",
|
|
128
|
+
"## Backend And Integration Changes",
|
|
129
|
+
"",
|
|
130
|
+
formatWindowItems(sections.backend, emptyCopy.backend, {
|
|
131
|
+
icon: "🖧",
|
|
132
|
+
sticker: "BACKEND",
|
|
133
|
+
emptyIcon: "🔌",
|
|
134
|
+
}),
|
|
135
|
+
"",
|
|
136
|
+
"---",
|
|
137
|
+
"class: retro-slide",
|
|
138
|
+
"---",
|
|
139
|
+
"",
|
|
140
|
+
"## Review Evidence",
|
|
141
|
+
"",
|
|
142
|
+
`<div class="slide-subtitle">Changed files used to generate this presentation.</div>`,
|
|
143
|
+
"",
|
|
144
|
+
formatFiles(changedFiles),
|
|
145
|
+
"",
|
|
146
|
+
].join("\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function classifyChangedFiles(changedFiles) {
|
|
150
|
+
const sections = normalizeSections({});
|
|
151
|
+
|
|
152
|
+
for (const file of changedFiles) {
|
|
153
|
+
const item = {
|
|
154
|
+
label: humanizeFile(file),
|
|
155
|
+
detail: file,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (isProto(file)) {
|
|
159
|
+
sections.protos.push(item);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isDatabase(file)) {
|
|
163
|
+
sections.database.push(item);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (isBehavior(file)) {
|
|
167
|
+
sections.behaviors.push({
|
|
168
|
+
label: catchyBehaviorLabel(file),
|
|
169
|
+
detail: file,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (isDesignSystem(file)) {
|
|
174
|
+
sections.designSystem.push(item);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (isPage(file)) {
|
|
178
|
+
sections.pages.push(item);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (isBackend(file)) {
|
|
182
|
+
sections.backend.push(item);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return sections;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function inferPurpose(changedFiles) {
|
|
190
|
+
if (changedFiles.length === 0) {
|
|
191
|
+
return "This PR is ready to be narrated once branch changes are available.";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const sections = classifyChangedFiles(changedFiles);
|
|
195
|
+
const visibleCount =
|
|
196
|
+
sections.pages.length + sections.behaviors.length + sections.designSystem.length;
|
|
197
|
+
const technicalCount =
|
|
198
|
+
sections.backend.length + sections.database.length + sections.protos.length;
|
|
199
|
+
|
|
200
|
+
if (visibleCount > 0 && technicalCount > 0) {
|
|
201
|
+
return "This PR connects product-facing polish with the backend contracts that make it real.";
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (visibleCount > 0) {
|
|
205
|
+
return "This PR sharpens what users see and protects the behavior reviewers care about.";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return "This PR improves the technical foundation while keeping reviewer attention on the important moving parts.";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function normalizeSections(sections) {
|
|
212
|
+
return {
|
|
213
|
+
pages: sections.pages ?? [],
|
|
214
|
+
behaviors: sections.behaviors ?? [],
|
|
215
|
+
designSystem: sections.designSystem ?? [],
|
|
216
|
+
backend: sections.backend ?? [],
|
|
217
|
+
database: sections.database ?? [],
|
|
218
|
+
protos: sections.protos ?? [],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function formatWindowItems(items, emptyText, options) {
|
|
223
|
+
if (items.length === 0) {
|
|
224
|
+
return emptyDesk(emptyText, options.emptyIcon);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return prioritizeItems(items)
|
|
228
|
+
.slice(0, 6)
|
|
229
|
+
.map(
|
|
230
|
+
(item) => cardHtml(item, options),
|
|
231
|
+
)
|
|
232
|
+
.join("\n\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function cardHtml(item, options) {
|
|
236
|
+
return `<div class="win95-window evidence-window"><div class="win95-titlebar"><span>${options.icon} ${escapeHtml(item.label)}</span><span>□ ×</span></div><div class="window-body"><div class="sticker">${options.sticker}</div>${formatScreenshot(item)}<p>${escapeHtml(item.detail || "Reviewer-visible change detected.")}</p><div class="evidence">Evidence: <code>${escapeHtml(item.detail || item.label)}</code></div></div></div>`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function formatApiOverview(items, emptyText) {
|
|
240
|
+
const services = protoServices(items);
|
|
241
|
+
|
|
242
|
+
if (services.length === 0) {
|
|
243
|
+
return emptyDesk(emptyText, "📄");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return `<div class="feature-overview">${services
|
|
247
|
+
.map(
|
|
248
|
+
({ item, service }) =>
|
|
249
|
+
`<div class="feature-summary-card"><span class="feature-badge">SERVICE</span><strong>${escapeHtml(service.name)}</strong><span>${service.rpcs.length} RPC${service.rpcs.length === 1 ? "" : "s"} in ${escapeHtml(item.detail)}</span></div>`,
|
|
250
|
+
)
|
|
251
|
+
.join("")}</div>`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function formatProtoServiceSlides(items) {
|
|
255
|
+
return protoServices(items).flatMap(({ item, service }) => [
|
|
256
|
+
"---",
|
|
257
|
+
"class: retro-slide",
|
|
258
|
+
"---",
|
|
259
|
+
"",
|
|
260
|
+
`## ${escapeHtml(service.name)}`,
|
|
261
|
+
"",
|
|
262
|
+
`<div class="slide-subtitle">This service exposes the application contract used by the frontend.</div>`,
|
|
263
|
+
"",
|
|
264
|
+
`<div class="win95-window evidence-window"><div class="win95-titlebar"><span>📡 RPC service</span><span>□ ×</span></div><div class="window-body"><div class="sticker">PROTO</div><p>We added this service with these RPCs:</p><ul>${service.rpcs
|
|
265
|
+
.map(
|
|
266
|
+
(rpc) =>
|
|
267
|
+
`<li><strong>${escapeHtml(rpc.name)}</strong>: ${escapeHtml(rpc.description)}</li>`,
|
|
268
|
+
)
|
|
269
|
+
.join("")}</ul><div class="evidence">Evidence: <code>${escapeHtml(item.detail)}</code></div></div></div>`,
|
|
270
|
+
"",
|
|
271
|
+
]);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function protoServices(items) {
|
|
275
|
+
return prioritizeItems(items).flatMap((item) =>
|
|
276
|
+
(item.protoServices ?? []).map((service) => ({ item, service })),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function formatDatabaseOverview(items, emptyText) {
|
|
281
|
+
const migrations = migrationItems(items);
|
|
282
|
+
|
|
283
|
+
if (migrations.length === 0) {
|
|
284
|
+
return emptyDesk(emptyText, "🗂️");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return `<div class="feature-overview">${migrations
|
|
288
|
+
.map(
|
|
289
|
+
(item) =>
|
|
290
|
+
`<div class="feature-summary-card"><span class="feature-badge">MIGRATION</span><strong>${escapeHtml(item.label)}</strong><span>${escapeHtml((item.migrationSummary ?? [item.detail])[0])}</span></div>`,
|
|
291
|
+
)
|
|
292
|
+
.join("")}</div>`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function formatMigrationSlides(items) {
|
|
296
|
+
return migrationItems(items).flatMap((item) => [
|
|
297
|
+
"---",
|
|
298
|
+
"class: retro-slide",
|
|
299
|
+
"---",
|
|
300
|
+
"",
|
|
301
|
+
`## ${escapeHtml(item.label)}`,
|
|
302
|
+
"",
|
|
303
|
+
`<div class="slide-subtitle">Database migration applied by this PR.</div>`,
|
|
304
|
+
"",
|
|
305
|
+
`<div class="win95-window evidence-window"><div class="win95-titlebar"><span>🗄️ Migration</span><span>□ ×</span></div><div class="window-body"><div class="sticker">DB</div><p>This migration changes the database as follows:</p><ul>${(item.migrationSummary ?? [item.detail])
|
|
306
|
+
.map((summary) => `<li>${escapeHtml(summary)}</li>`)
|
|
307
|
+
.join("")}</ul><div class="evidence">Evidence: <code>${escapeHtml(item.detail)}</code></div></div></div>`,
|
|
308
|
+
"",
|
|
309
|
+
]);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function migrationItems(items) {
|
|
313
|
+
return prioritizeItems(items).filter(
|
|
314
|
+
(item) => item.detail?.includes("/migrations/") && item.detail.endsWith(".sql"),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function formatBehaviorItems(items, emptyText) {
|
|
319
|
+
return formatFeatureOverview(featureItemsFromBehaviors(items), emptyText);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function formatFeatureOverview(items, emptyText) {
|
|
323
|
+
if (items.length === 0) {
|
|
324
|
+
return emptyDesk(emptyText, "✅");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return `<div class="feature-overview">
|
|
328
|
+
${items
|
|
329
|
+
.slice(0, 6)
|
|
330
|
+
.map(
|
|
331
|
+
(item) => `<div class="feature-summary-card">
|
|
332
|
+
<span class="feature-badge">FEATURE</span>
|
|
333
|
+
<strong>${escapeHtml(item.label)}</strong>
|
|
334
|
+
<span>${escapeHtml(item.summary)}</span>
|
|
335
|
+
</div>`,
|
|
336
|
+
)
|
|
337
|
+
.join("\n")}
|
|
338
|
+
</div>`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function formatFeatureSlides(items) {
|
|
342
|
+
return items
|
|
343
|
+
.slice(0, 6)
|
|
344
|
+
.flatMap((item) => [
|
|
345
|
+
"---",
|
|
346
|
+
"class: retro-slide",
|
|
347
|
+
"---",
|
|
348
|
+
"",
|
|
349
|
+
`## ${escapeHtml(item.label)}`,
|
|
350
|
+
"",
|
|
351
|
+
`<div class="slide-subtitle">${escapeHtml(item.summary)}</div>`,
|
|
352
|
+
"",
|
|
353
|
+
`<div class="win95-window evidence-window feature-window"><div class="win95-titlebar"><span>✨ Feature specification</span><span>□ ×</span></div><div class="window-body"><div class="sticker">FEATURE</div>${formatScreenshot(item)}<p>${escapeHtml(item.description)}</p><div class="evidence">Evidence: <code>${escapeHtml(item.detail || item.label)}</code></div></div></div>`,
|
|
354
|
+
"",
|
|
355
|
+
]);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function featureItemsFromBehaviors(items) {
|
|
359
|
+
const featureItems = prioritizeItems(items).filter((item) =>
|
|
360
|
+
item.detail?.startsWith("e2e/features/"),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const sourceItems = featureItems.length > 0 ? featureItems : prioritizeItems(items);
|
|
364
|
+
|
|
365
|
+
return sourceItems.map((item) => {
|
|
366
|
+
const feature = featureCopy(item);
|
|
367
|
+
|
|
368
|
+
return {
|
|
369
|
+
...item,
|
|
370
|
+
label: feature.label,
|
|
371
|
+
summary: feature.summary,
|
|
372
|
+
description: feature.description,
|
|
373
|
+
};
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function featureCopy(item) {
|
|
378
|
+
const detail = item.detail ?? "";
|
|
379
|
+
const key = detail
|
|
380
|
+
.split("/")
|
|
381
|
+
.at(-1)
|
|
382
|
+
?.replace(/\.feature$/, "");
|
|
383
|
+
|
|
384
|
+
if (key === "helloworld") {
|
|
385
|
+
return {
|
|
386
|
+
label: "Say hello through the UI",
|
|
387
|
+
summary: "Users can submit a name and see the server-generated greeting.",
|
|
388
|
+
description:
|
|
389
|
+
"The hello-world flow proves the browser, ConnectRPC, Rust server, and SQLite persistence path work together.",
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
label: item.label.replace(/: the user promise$/, ""),
|
|
395
|
+
summary: item.detail || "A user-facing capability is described by the e2e specification.",
|
|
396
|
+
description: item.detail || "This feature is included in the end-to-end product specification.",
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function formatStickerItems(items, emptyText) {
|
|
401
|
+
if (items.length === 0) {
|
|
402
|
+
return emptyDesk(emptyText, "🏷️");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return `<div class="sticker-sheet">
|
|
406
|
+
${prioritizeItems(items)
|
|
407
|
+
.slice(0, 10)
|
|
408
|
+
.map(
|
|
409
|
+
(item) =>
|
|
410
|
+
`<div class="component-sticker"><div class="sticker-emoji">🏷️</div>${formatScreenshot(item)}<strong>${escapeHtml(item.label)}</strong><span>${escapeHtml(item.detail || "Component evidence")}</span></div>`,
|
|
411
|
+
)
|
|
412
|
+
.join("\n")}
|
|
413
|
+
</div>`;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function prioritizeItems(items) {
|
|
417
|
+
return [...items].sort((left, right) => {
|
|
418
|
+
const imageScore = Number(Boolean(right.image)) - Number(Boolean(left.image));
|
|
419
|
+
|
|
420
|
+
if (imageScore !== 0) {
|
|
421
|
+
return imageScore;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const featureScore =
|
|
425
|
+
Number(right.detail?.startsWith("e2e/features/")) -
|
|
426
|
+
Number(left.detail?.startsWith("e2e/features/"));
|
|
427
|
+
|
|
428
|
+
if (featureScore !== 0) {
|
|
429
|
+
return featureScore;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
return 0;
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function formatScreenshot(item) {
|
|
437
|
+
if (!item.image) {
|
|
438
|
+
return "";
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return `<img class="deck-screenshot" src="${escapeHtml(item.image)}" alt="${escapeHtml(item.label)} screenshot" />`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function formatInlineScreenshot(item) {
|
|
445
|
+
if (!item.image) {
|
|
446
|
+
return "";
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return `<img class="journey-screenshot" src="${escapeHtml(item.image)}" alt="${escapeHtml(item.label)} screenshot" />`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function emptyDesk(emptyText, icon) {
|
|
453
|
+
return `<div class="empty-desk">
|
|
454
|
+
<div class="empty-icon">${icon}</div>
|
|
455
|
+
<strong>${escapeHtml(emptyText)}</strong>
|
|
456
|
+
<span>The office has logged zero incidents in this bucket.</span>
|
|
457
|
+
</div>`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function formatFiles(files) {
|
|
461
|
+
if (files.length === 0) {
|
|
462
|
+
return emptyDesk("No focused changed-file list was provided.", "🧾");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return `<div class="evidence-grid">
|
|
466
|
+
${files.map((file) => `<code>${escapeHtml(file)}</code>`).join("\n")}
|
|
467
|
+
</div>`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function catchyPurpose(purpose) {
|
|
471
|
+
const trimmed = purpose.trim();
|
|
472
|
+
|
|
473
|
+
if (trimmed.endsWith(".")) {
|
|
474
|
+
return trimmed;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return trimmed + ".";
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function yamlString(value) {
|
|
481
|
+
return JSON.stringify(value);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function normalizeDate(value) {
|
|
485
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
486
|
+
|
|
487
|
+
return date.toISOString().slice(0, 10);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function escapeHtml(value) {
|
|
491
|
+
return String(value)
|
|
492
|
+
.replaceAll("&", "&")
|
|
493
|
+
.replaceAll("<", "<")
|
|
494
|
+
.replaceAll(">", ">")
|
|
495
|
+
.replaceAll('"', """);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function retroStyles() {
|
|
499
|
+
return `<style>
|
|
500
|
+
:root {
|
|
501
|
+
--bb-cream: #fff7d6;
|
|
502
|
+
--bb-paper: #fffce8;
|
|
503
|
+
--bb-ink: #171129;
|
|
504
|
+
--bb-cyan: #00d5ff;
|
|
505
|
+
--bb-pink: #ff4fd8;
|
|
506
|
+
--bb-purple: #6f42ff;
|
|
507
|
+
--bb-yellow: #ffe66d;
|
|
508
|
+
--bb-green: #3dfc89;
|
|
509
|
+
--bb-shadow: rgba(23, 17, 41, 0.35);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
.slidev-layout {
|
|
513
|
+
color: var(--bb-ink);
|
|
514
|
+
background:
|
|
515
|
+
linear-gradient(135deg, rgba(255, 79, 216, 0.18), rgba(0, 213, 255, 0.22)),
|
|
516
|
+
linear-gradient(45deg, rgba(255,255,255,.18) 25%, transparent 25% 50%, rgba(255,255,255,.18) 50% 75%, transparent 75%);
|
|
517
|
+
background-size: auto, 28px 28px;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.retro-stage {
|
|
521
|
+
min-height: 100%;
|
|
522
|
+
position: relative;
|
|
523
|
+
display: grid;
|
|
524
|
+
place-items: center;
|
|
525
|
+
padding: 42px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.title-stage {
|
|
529
|
+
background: radial-gradient(circle at 15% 20%, #fff 0 0.5rem, transparent 0.55rem), linear-gradient(135deg, #2cf6ff, #ff7ae6 54%, #ffe66d);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.memo-window,
|
|
533
|
+
.win95-window {
|
|
534
|
+
background: var(--bb-paper);
|
|
535
|
+
border: 4px solid var(--bb-ink);
|
|
536
|
+
box-shadow: 10px 10px 0 var(--bb-shadow);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.memo-window {
|
|
540
|
+
width: min(860px, 92%);
|
|
541
|
+
transform: rotate(-1deg);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.win95-titlebar {
|
|
545
|
+
display: flex;
|
|
546
|
+
justify-content: space-between;
|
|
547
|
+
gap: 24px;
|
|
548
|
+
background: linear-gradient(90deg, #1b2cff, #c600ff);
|
|
549
|
+
color: white;
|
|
550
|
+
font-family: "Courier New", monospace;
|
|
551
|
+
font-size: 15px;
|
|
552
|
+
font-weight: 800;
|
|
553
|
+
padding: 8px 12px;
|
|
554
|
+
text-transform: uppercase;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.memo-body {
|
|
558
|
+
padding: 38px;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.kicker,
|
|
562
|
+
.tiny-caption,
|
|
563
|
+
.evidence {
|
|
564
|
+
font-family: "Courier New", monospace;
|
|
565
|
+
text-transform: uppercase;
|
|
566
|
+
letter-spacing: 0;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.kicker {
|
|
570
|
+
color: #6f42ff;
|
|
571
|
+
font-weight: 900;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.purpose,
|
|
575
|
+
.big-promise {
|
|
576
|
+
font-size: 30px;
|
|
577
|
+
line-height: 1.15;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.floppy-label,
|
|
581
|
+
.stamp,
|
|
582
|
+
.sticker,
|
|
583
|
+
.promise-stamp {
|
|
584
|
+
display: inline-block;
|
|
585
|
+
border: 3px solid var(--bb-ink);
|
|
586
|
+
background: var(--bb-yellow);
|
|
587
|
+
box-shadow: 4px 4px 0 var(--bb-ink);
|
|
588
|
+
font-family: "Courier New", monospace;
|
|
589
|
+
font-weight: 900;
|
|
590
|
+
padding: 8px 12px;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.stamp {
|
|
594
|
+
position: absolute;
|
|
595
|
+
top: 30px;
|
|
596
|
+
right: 42px;
|
|
597
|
+
transform: rotate(5deg);
|
|
598
|
+
color: #d10072;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.paperclip {
|
|
602
|
+
position: absolute;
|
|
603
|
+
left: 40px;
|
|
604
|
+
bottom: 30px;
|
|
605
|
+
font-size: 72px;
|
|
606
|
+
animation: officeFloat 3.5s ease-in-out infinite;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.split-stage {
|
|
610
|
+
grid-template-columns: 1.1fr 0.9fr;
|
|
611
|
+
gap: 44px;
|
|
612
|
+
align-items: center;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.office-oracle {
|
|
616
|
+
background: #d8d8d8;
|
|
617
|
+
border: 4px solid var(--bb-ink);
|
|
618
|
+
box-shadow: 8px 8px 0 var(--bb-shadow);
|
|
619
|
+
padding: 28px;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.oracle-face {
|
|
623
|
+
font-size: 88px;
|
|
624
|
+
text-align: center;
|
|
625
|
+
animation: officeFloat 4s ease-in-out infinite;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
.speech-bubble {
|
|
629
|
+
background: white;
|
|
630
|
+
border: 3px solid var(--bb-ink);
|
|
631
|
+
padding: 18px;
|
|
632
|
+
font-size: 24px;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
.slide-subtitle {
|
|
636
|
+
margin: 0 0 24px;
|
|
637
|
+
font-size: 22px;
|
|
638
|
+
background: rgba(255, 255, 255, 0.72);
|
|
639
|
+
border-left: 8px solid var(--bb-pink);
|
|
640
|
+
padding: 12px 16px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.evidence-window {
|
|
644
|
+
margin: 12px 0;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.deck-screenshot {
|
|
648
|
+
display: block;
|
|
649
|
+
width: min(100%, 720px);
|
|
650
|
+
max-height: 260px;
|
|
651
|
+
object-fit: contain;
|
|
652
|
+
object-position: top left;
|
|
653
|
+
border: 3px solid var(--bb-ink);
|
|
654
|
+
box-shadow: 5px 5px 0 rgba(23, 17, 41, 0.22);
|
|
655
|
+
background: white;
|
|
656
|
+
margin: 0 0 14px;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.journey-screenshot {
|
|
660
|
+
display: block;
|
|
661
|
+
width: min(82%, 620px);
|
|
662
|
+
max-height: 220px;
|
|
663
|
+
object-fit: contain;
|
|
664
|
+
border: 3px solid var(--bb-ink);
|
|
665
|
+
box-shadow: 5px 5px 0 rgba(23, 17, 41, 0.22);
|
|
666
|
+
background: white;
|
|
667
|
+
margin: 10px 0 12px 36px;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.window-body {
|
|
671
|
+
position: relative;
|
|
672
|
+
padding: 18px 20px;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
.sticker {
|
|
676
|
+
position: absolute;
|
|
677
|
+
top: 12px;
|
|
678
|
+
right: 12px;
|
|
679
|
+
background: var(--bb-green);
|
|
680
|
+
transform: rotate(3deg);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.evidence {
|
|
684
|
+
color: #50366f;
|
|
685
|
+
font-size: 12px;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.promise-stamp {
|
|
689
|
+
margin-right: 10px;
|
|
690
|
+
background: #ff9be8;
|
|
691
|
+
font-size: 13px;
|
|
692
|
+
padding: 3px 7px;
|
|
693
|
+
box-shadow: 2px 2px 0 var(--bb-ink);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.sticker-sheet,
|
|
697
|
+
.evidence-grid {
|
|
698
|
+
display: grid;
|
|
699
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
700
|
+
gap: 14px;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.component-sticker {
|
|
704
|
+
background: #fff;
|
|
705
|
+
border: 3px dashed var(--bb-ink);
|
|
706
|
+
box-shadow: 5px 5px 0 var(--bb-shadow);
|
|
707
|
+
padding: 16px;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
.component-sticker span,
|
|
711
|
+
.component-sticker strong {
|
|
712
|
+
display: block;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.sticker-emoji {
|
|
716
|
+
font-size: 30px;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.empty-desk {
|
|
720
|
+
display: grid;
|
|
721
|
+
place-items: center;
|
|
722
|
+
gap: 8px;
|
|
723
|
+
min-height: 260px;
|
|
724
|
+
background: rgba(255, 252, 232, 0.9);
|
|
725
|
+
border: 4px dotted var(--bb-ink);
|
|
726
|
+
box-shadow: 8px 8px 0 var(--bb-shadow);
|
|
727
|
+
text-align: center;
|
|
728
|
+
padding: 28px;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.empty-icon {
|
|
732
|
+
font-size: 64px;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.basement-title {
|
|
736
|
+
background: linear-gradient(135deg, #f0e2c0, #8fd3ff);
|
|
737
|
+
text-align: center;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.warning-sign {
|
|
741
|
+
font-size: 90px;
|
|
742
|
+
animation: officeFloat 3s ease-in-out infinite;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.evidence-grid code {
|
|
746
|
+
display: block;
|
|
747
|
+
overflow-wrap: anywhere;
|
|
748
|
+
background: #111;
|
|
749
|
+
color: #89ffcb;
|
|
750
|
+
border: 2px solid var(--bb-pink);
|
|
751
|
+
padding: 8px;
|
|
752
|
+
font-size: 11px;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
@keyframes officeFloat {
|
|
756
|
+
0%, 100% { transform: translateY(0) rotate(-2deg); }
|
|
757
|
+
50% { transform: translateY(-10px) rotate(2deg); }
|
|
758
|
+
}
|
|
759
|
+
</style>`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function humanizeFile(file) {
|
|
763
|
+
const name = file.split("/").at(-1) ?? file;
|
|
764
|
+
const withoutExtension = name.replace(/\.[^.]+$/, "");
|
|
765
|
+
|
|
766
|
+
return withoutExtension
|
|
767
|
+
.split(/[-_.]/)
|
|
768
|
+
.filter(Boolean)
|
|
769
|
+
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
770
|
+
.join(" ");
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function catchyBehaviorLabel(file) {
|
|
774
|
+
const label = humanizeFile(file);
|
|
775
|
+
|
|
776
|
+
if (file.includes("e2e/")) {
|
|
777
|
+
return `${label}: the user promise`;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
return `${label}: the safety net`;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function isProto(file) {
|
|
784
|
+
return file.startsWith("proto/") || file.endsWith(".proto");
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
function isDatabase(file) {
|
|
788
|
+
return file.includes("migration") || file.includes("migrations/") || file.endsWith(".sql");
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function isBehavior(file) {
|
|
792
|
+
return (
|
|
793
|
+
file.startsWith("e2e/") ||
|
|
794
|
+
file.endsWith(".spec.ts") ||
|
|
795
|
+
file.endsWith(".spec.tsx") ||
|
|
796
|
+
file.endsWith(".test.ts") ||
|
|
797
|
+
file.endsWith(".test.tsx")
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function isDesignSystem(file) {
|
|
802
|
+
return (
|
|
803
|
+
file.includes("design-system") ||
|
|
804
|
+
file.endsWith(".stories.ts") ||
|
|
805
|
+
file.endsWith(".stories.tsx")
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function isPage(file) {
|
|
810
|
+
if (
|
|
811
|
+
file.endsWith(".stories.ts") ||
|
|
812
|
+
file.endsWith(".stories.tsx") ||
|
|
813
|
+
file.endsWith(".test.ts") ||
|
|
814
|
+
file.endsWith(".test.tsx")
|
|
815
|
+
) {
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return (
|
|
820
|
+
file.includes("/pages/") ||
|
|
821
|
+
file.includes("/routes/") ||
|
|
822
|
+
file.includes("router") ||
|
|
823
|
+
file.includes("route")
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
function isBackend(file) {
|
|
828
|
+
if (isDatabase(file)) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return file.startsWith("server/") || file.endsWith(".rs");
|
|
833
|
+
}
|