@starci/skills 1.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/INDEX.md +74 -0
- package/INDEX.vi.md +75 -0
- package/README.md +44 -0
- package/README.vi.md +43 -0
- package/SKILL.md +135 -0
- package/SKILL.vi.md +128 -0
- package/alias/INDEX.md +104 -0
- package/alias/INDEX.vi.md +104 -0
- package/alias/alias.json +297 -0
- package/bin/starci-skills.mjs +219 -0
- package/knowledge/INDEX.md +22 -0
- package/knowledge/INDEX.vi.md +22 -0
- package/knowledge/grammars/starci/DNA.md +146 -0
- package/knowledge/grammars/starci/DNA.vi.md +146 -0
- package/knowledge/grammars/starci/INDEX.md +25 -0
- package/knowledge/grammars/starci/INDEX.vi.md +25 -0
- package/knowledge/grammars/starci/family.md +50 -0
- package/knowledge/grammars/starci/family.vi.md +50 -0
- package/knowledge/grammars/starci/idioms.md +120 -0
- package/knowledge/grammars/starci/idioms.vi.md +121 -0
- package/knowledge/grammars/starci/playbook.md +36 -0
- package/knowledge/grammars/starci/playbook.vi.md +35 -0
- package/knowledge/patterns/be/INDEX.md +29 -0
- package/knowledge/patterns/be/INDEX.vi.md +29 -0
- package/knowledge/patterns/be/comment.md +80 -0
- package/knowledge/patterns/be/comment.vi.md +80 -0
- package/knowledge/patterns/be/error.md +86 -0
- package/knowledge/patterns/be/error.vi.md +86 -0
- package/knowledge/patterns/be/folder.md +87 -0
- package/knowledge/patterns/be/folder.vi.md +86 -0
- package/knowledge/patterns/be/function.md +80 -0
- package/knowledge/patterns/be/function.vi.md +80 -0
- package/knowledge/patterns/be/imports.md +79 -0
- package/knowledge/patterns/be/imports.vi.md +79 -0
- package/knowledge/patterns/be/naming.md +87 -0
- package/knowledge/patterns/be/naming.vi.md +87 -0
- package/knowledge/patterns/be/test.md +79 -0
- package/knowledge/patterns/be/test.vi.md +79 -0
- package/knowledge/patterns/be/typing.md +73 -0
- package/knowledge/patterns/be/typing.vi.md +73 -0
- package/knowledge/patterns/fe/INDEX.md +29 -0
- package/knowledge/patterns/fe/INDEX.vi.md +29 -0
- package/knowledge/patterns/fe/comment.md +70 -0
- package/knowledge/patterns/fe/comment.vi.md +70 -0
- package/knowledge/patterns/fe/error.md +61 -0
- package/knowledge/patterns/fe/error.vi.md +61 -0
- package/knowledge/patterns/fe/folder.md +98 -0
- package/knowledge/patterns/fe/folder.vi.md +95 -0
- package/knowledge/patterns/fe/function.md +70 -0
- package/knowledge/patterns/fe/function.vi.md +70 -0
- package/knowledge/patterns/fe/imports.md +87 -0
- package/knowledge/patterns/fe/imports.vi.md +87 -0
- package/knowledge/patterns/fe/naming.md +77 -0
- package/knowledge/patterns/fe/naming.vi.md +77 -0
- package/knowledge/patterns/fe/test.md +73 -0
- package/knowledge/patterns/fe/test.vi.md +73 -0
- package/knowledge/patterns/fe/typing.md +67 -0
- package/knowledge/patterns/fe/typing.vi.md +67 -0
- package/knowledge/ui/INDEX.md +101 -0
- package/knowledge/ui/INDEX.vi.md +101 -0
- package/knowledge/ui/composition/INDEX.md +60 -0
- package/knowledge/ui/composition/INDEX.vi.md +63 -0
- package/knowledge/ui/composition/accent.md +73 -0
- package/knowledge/ui/composition/accent.vi.md +74 -0
- package/knowledge/ui/composition/action.md +55 -0
- package/knowledge/ui/composition/action.vi.md +56 -0
- package/knowledge/ui/composition/coverage.md +37 -0
- package/knowledge/ui/composition/coverage.vi.md +37 -0
- package/knowledge/ui/composition/cta.md +79 -0
- package/knowledge/ui/composition/cta.vi.md +79 -0
- package/knowledge/ui/composition/feedback.md +55 -0
- package/knowledge/ui/composition/feedback.vi.md +56 -0
- package/knowledge/ui/composition/hierarchy.md +83 -0
- package/knowledge/ui/composition/hierarchy.vi.md +82 -0
- package/knowledge/ui/composition/layout.md +91 -0
- package/knowledge/ui/composition/layout.vi.md +93 -0
- package/knowledge/ui/composition/responsive.md +67 -0
- package/knowledge/ui/composition/responsive.vi.md +67 -0
- package/knowledge/ui/composition/state.md +105 -0
- package/knowledge/ui/composition/state.vi.md +106 -0
- package/knowledge/ui/presentation/INDEX.md +94 -0
- package/knowledge/ui/presentation/INDEX.vi.md +69 -0
- package/knowledge/ui/presentation/boundary.md +170 -0
- package/knowledge/ui/presentation/boundary.vi.md +169 -0
- package/knowledge/ui/presentation/font.md +155 -0
- package/knowledge/ui/presentation/font.vi.md +156 -0
- package/knowledge/ui/presentation/gap.md +181 -0
- package/knowledge/ui/presentation/gap.vi.md +181 -0
- package/knowledge/ui/presentation/margin.md +168 -0
- package/knowledge/ui/presentation/margin.vi.md +171 -0
- package/knowledge/ui/presentation/measure.md +174 -0
- package/knowledge/ui/presentation/measure.vi.md +178 -0
- package/knowledge/ui/presentation/overflow.md +142 -0
- package/knowledge/ui/presentation/overflow.vi.md +142 -0
- package/knowledge/ui/presentation/padding.md +293 -0
- package/knowledge/ui/presentation/padding.vi.md +292 -0
- package/knowledge/ui/presentation/surface.md +166 -0
- package/knowledge/ui/presentation/surface.vi.md +166 -0
- package/knowledge/ui/presentation/text-flow.md +123 -0
- package/knowledge/ui/presentation/text-flow.vi.md +123 -0
- package/knowledge/ui/presentation/tone.md +114 -0
- package/knowledge/ui/presentation/tone.vi.md +113 -0
- package/knowledge/ui/proof/INDEX.md +55 -0
- package/knowledge/ui/proof/INDEX.vi.md +55 -0
- package/knowledge/ui/proof/accessibility.md +67 -0
- package/knowledge/ui/proof/accessibility.vi.md +68 -0
- package/knowledge/ui/proof/contrast.md +58 -0
- package/knowledge/ui/proof/contrast.vi.md +57 -0
- package/knowledge/ui/proof/focus.md +76 -0
- package/knowledge/ui/proof/focus.vi.md +76 -0
- package/knowledge/ui/proof/motion.md +63 -0
- package/knowledge/ui/proof/motion.vi.md +64 -0
- package/knowledge/ui/proof/render-truth.md +63 -0
- package/knowledge/ui/proof/render-truth.vi.md +63 -0
- package/operators/INDEX.md +199 -0
- package/operators/INDEX.vi.md +199 -0
- package/operators/architecture-decide/errors.json +115 -0
- package/operators/architecture-decide/operator.json +20 -0
- package/operators/architecture-decide/operator.md +133 -0
- package/operators/architecture-decide/operator.vi.md +130 -0
- package/operators/architecture-decide/self-test.mjs +221 -0
- package/operators/architecture-decide/validate.mjs +112 -0
- package/operators/backend-source-apply/errors.json +66 -0
- package/operators/backend-source-apply/operator.json +20 -0
- package/operators/backend-source-apply/operator.md +189 -0
- package/operators/backend-source-apply/operator.vi.md +185 -0
- package/operators/backend-source-apply/self-test.mjs +257 -0
- package/operators/backend-source-apply/validate.mjs +240 -0
- package/operators/business-decide/errors.json +78 -0
- package/operators/business-decide/operator.json +20 -0
- package/operators/business-decide/operator.md +176 -0
- package/operators/business-decide/operator.vi.md +174 -0
- package/operators/business-decide/self-test.mjs +225 -0
- package/operators/business-decide/validate.mjs +277 -0
- package/operators/content-generate/errors.json +106 -0
- package/operators/content-generate/operator.json +21 -0
- package/operators/content-generate/operator.md +155 -0
- package/operators/content-generate/operator.vi.md +155 -0
- package/operators/content-generate/self-test.mjs +288 -0
- package/operators/content-generate/validate.mjs +196 -0
- package/operators/errors.json +178 -0
- package/operators/frontend-direction-decide/errors.json +135 -0
- package/operators/frontend-direction-decide/operator.json +21 -0
- package/operators/frontend-direction-decide/operator.md +167 -0
- package/operators/frontend-direction-decide/operator.vi.md +167 -0
- package/operators/frontend-direction-decide/self-test.mjs +226 -0
- package/operators/frontend-direction-decide/validate.mjs +146 -0
- package/operators/frontend-presentation-resolve/errors.json +42 -0
- package/operators/frontend-presentation-resolve/operator.json +19 -0
- package/operators/frontend-presentation-resolve/operator.md +142 -0
- package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
- package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
- package/operators/frontend-presentation-resolve/validate.mjs +143 -0
- package/operators/frontend-source-apply/errors.json +30 -0
- package/operators/frontend-source-apply/operator.json +20 -0
- package/operators/frontend-source-apply/operator.md +127 -0
- package/operators/frontend-source-apply/operator.vi.md +126 -0
- package/operators/frontend-source-apply/self-test.mjs +214 -0
- package/operators/frontend-source-apply/validate.mjs +133 -0
- package/operators/frontend-surface-audit/errors.json +5 -0
- package/operators/frontend-surface-audit/operator.json +22 -0
- package/operators/frontend-surface-audit/operator.md +121 -0
- package/operators/frontend-surface-audit/operator.vi.md +121 -0
- package/operators/frontend-surface-audit/self-test.mjs +185 -0
- package/operators/frontend-surface-audit/validate.mjs +127 -0
- package/operators/git-publish/errors.json +54 -0
- package/operators/git-publish/operator.json +20 -0
- package/operators/git-publish/operator.md +180 -0
- package/operators/git-publish/operator.vi.md +178 -0
- package/operators/git-publish/self-test.mjs +177 -0
- package/operators/git-publish/validate.mjs +106 -0
- package/operators/platform-operate/errors.json +90 -0
- package/operators/platform-operate/operator.json +22 -0
- package/operators/platform-operate/operator.md +162 -0
- package/operators/platform-operate/operator.vi.md +160 -0
- package/operators/platform-operate/self-test.mjs +202 -0
- package/operators/platform-operate/validate.mjs +193 -0
- package/operators/quality-verify/errors.json +54 -0
- package/operators/quality-verify/operator.json +20 -0
- package/operators/quality-verify/operator.md +185 -0
- package/operators/quality-verify/operator.vi.md +177 -0
- package/operators/quality-verify/self-test.mjs +210 -0
- package/operators/quality-verify/validate.mjs +197 -0
- package/operators/release-deploy/errors.json +158 -0
- package/operators/release-deploy/operator.json +23 -0
- package/operators/release-deploy/operator.md +183 -0
- package/operators/release-deploy/operator.vi.md +181 -0
- package/operators/release-deploy/self-test.mjs +235 -0
- package/operators/release-deploy/validate.mjs +129 -0
- package/operators/uat-verify/errors.json +78 -0
- package/operators/uat-verify/operator.json +25 -0
- package/operators/uat-verify/operator.md +162 -0
- package/operators/uat-verify/operator.vi.md +161 -0
- package/operators/uat-verify/self-test.mjs +270 -0
- package/operators/uat-verify/validate.mjs +202 -0
- package/operators/workspace-bind/errors.json +90 -0
- package/operators/workspace-bind/operator.json +21 -0
- package/operators/workspace-bind/operator.md +148 -0
- package/operators/workspace-bind/operator.vi.md +148 -0
- package/operators/workspace-bind/self-test.mjs +205 -0
- package/operators/workspace-bind/validate.mjs +152 -0
- package/package.json +50 -0
- package/readiness/initialization/workspaces/commit-policy.json +91 -0
- package/readiness/initialization/workspaces/config.schema.json +35 -0
- package/readiness/initialization/workspaces/device-state.schema.json +54 -0
- package/readiness/initialization/workspaces/local-route.schema.json +206 -0
- package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
- package/resources/INDEX.md +96 -0
- package/resources/INDEX.vi.md +99 -0
- package/resources/agents/profiles/claude.json +131 -0
- package/resources/agents/profiles/openai.json +131 -0
- package/resources/orchestrator.json +71 -0
- package/resources/tools.json +85 -0
- package/routing.json +272 -0
- package/scripts/alias-registry.mjs +31 -0
- package/scripts/device-state.mjs +497 -0
- package/scripts/device-state.spec.mjs +18 -0
- package/scripts/errors-registry.mjs +68 -0
- package/scripts/generate-alias-doc.mjs +66 -0
- package/scripts/generate-grammar-dna.mjs +387 -0
- package/scripts/generate-operators-index.mjs +95 -0
- package/scripts/generate-presentation-owned.mjs +681 -0
- package/scripts/install-cli.spec.mjs +74 -0
- package/scripts/json-schema.mjs +94 -0
- package/scripts/operator-md.mjs +96 -0
- package/scripts/run-operator-self-tests.mjs +36 -0
- package/scripts/validate-alias.mjs +165 -0
- package/scripts/validate-defaults.mjs +72 -0
- package/scripts/validate-knowledge-citations.mjs +90 -0
- package/scripts/validate-operator.mjs +125 -0
- package/scripts/validate-request.mjs +80 -0
- package/scripts/validate-resources.mjs +117 -0
- package/scripts/validate-response.mjs +141 -0
- package/scripts/validate-routing.mjs +91 -0
- package/scripts/validate-step.mjs +50 -0
- package/scripts/validate-templates.mjs +226 -0
- package/scripts/validate-templates.spec.mjs +144 -0
- package/scripts/validate-workflows.mjs +106 -0
- package/scripts/workspace-portable.mjs +389 -0
- package/scripts/workspace-portable.spec.mjs +246 -0
- package/templates/README.md +47 -0
- package/templates/README.vi.md +45 -0
- package/templates/changes.example.md +27 -0
- package/templates/grammars.template.md +34 -0
- package/templates/kinds/architecture-decision.contract.json +14 -0
- package/templates/kinds/architecture-decision.skeleton.md +52 -0
- package/templates/kinds/backend-source-application.contract.json +10 -0
- package/templates/kinds/backend-source-application.skeleton.md +34 -0
- package/templates/kinds/business-promise-authority.contract.json +13 -0
- package/templates/kinds/business-promise-authority.skeleton.md +67 -0
- package/templates/kinds/capture.schema.json +33 -0
- package/templates/kinds/changes.contract.json +10 -0
- package/templates/kinds/changes.skeleton.md +26 -0
- package/templates/kinds/checks.schema.json +44 -0
- package/templates/kinds/claims.schema.json +165 -0
- package/templates/kinds/conformance.schema.json +15 -0
- package/templates/kinds/content-brief.contract.json +12 -0
- package/templates/kinds/content-brief.skeleton.md +45 -0
- package/templates/kinds/content-generation-receipt.contract.json +11 -0
- package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
- package/templates/kinds/content-review.contract.json +11 -0
- package/templates/kinds/content-review.skeleton.md +45 -0
- package/templates/kinds/contract.schema.json +28 -0
- package/templates/kinds/coverage-matrix.schema.json +86 -0
- package/templates/kinds/coverage.schema.json +62 -0
- package/templates/kinds/current-state.schema.json +40 -0
- package/templates/kinds/delta.schema.json +95 -0
- package/templates/kinds/e2e.schema.json +63 -0
- package/templates/kinds/frontend-direction-decision.contract.json +93 -0
- package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
- package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
- package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
- package/templates/kinds/frontend-source-application.contract.json +10 -0
- package/templates/kinds/frontend-source-application.skeleton.md +30 -0
- package/templates/kinds/frontend-surface-audit.contract.json +52 -0
- package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
- package/templates/kinds/gate-result.schema.json +64 -0
- package/templates/kinds/git-publication.contract.json +82 -0
- package/templates/kinds/git-publication.skeleton.md +61 -0
- package/templates/kinds/independent-critique.contract.json +9 -0
- package/templates/kinds/independent-critique.skeleton.md +28 -0
- package/templates/kinds/inventory.schema.json +35 -0
- package/templates/kinds/model.schema.json +99 -0
- package/templates/kinds/mutations.schema.json +65 -0
- package/templates/kinds/platform-operation-receipt.contract.json +76 -0
- package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
- package/templates/kinds/probes.schema.json +130 -0
- package/templates/kinds/proof.schema.json +17 -0
- package/templates/kinds/quality-verification.contract.json +87 -0
- package/templates/kinds/quality-verification.skeleton.md +58 -0
- package/templates/kinds/release-deployment.contract.json +85 -0
- package/templates/kinds/release-deployment.skeleton.md +67 -0
- package/templates/kinds/route.schema.json +293 -0
- package/templates/kinds/stack-model.schema.json +90 -0
- package/templates/kinds/uat-capture.schema.json +35 -0
- package/templates/kinds/uat-flow-verification.contract.json +11 -0
- package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
- package/templates/kinds/uat-snapshot.schema.json +96 -0
- package/templates/kinds/uat-verdicts.schema.json +41 -0
- package/templates/kinds/ui-coverage.schema.json +77 -0
- package/templates/kinds/verdicts.schema.json +39 -0
- package/templates/kinds/workspace-route-binding.contract.json +12 -0
- package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
- package/templates/kinds/writes.schema.json +37 -0
- package/templates/operator.template.md +73 -0
- package/templates/patterns.template.md +31 -0
- package/templates/step/request.schema.json +42 -0
- package/templates/step/response.schema.json +144 -0
- package/templates/ui-composition.template.md +36 -0
- package/templates/ui-presentation.template.md +57 -0
- package/templates/ui-proof.template.md +34 -0
- package/workflows/README.md +37 -0
- package/workflows/README.vi.md +37 -0
- package/workflows/backend-feature.json +59 -0
- package/workflows/content-unit.json +19 -0
- package/workflows/frontend-new-surface.json +81 -0
- package/workflows/frontend-reconstruct.json +67 -0
- package/workflows/frontend-refine.json +67 -0
- package/workflows/frontend-with-uat.json +78 -0
- package/workflows/full-feature.json +104 -0
- package/workflows/release.json +29 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Comment
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a frontend declaration or statement, does it carry a
|
|
4
|
+
comment, and what does that comment say?
|
|
5
|
+
|
|
6
|
+
Sources: `src/components/pages/AuthenticationPage/*`, `blocks/ai/StarCiAiChat/*`,
|
|
7
|
+
`blocks/ai/CourseAdvisorRecommendationCard/*`, `hooks/index.ts`, `hooks/swr/useQueryCourseSwr.ts`,
|
|
8
|
+
`hooks/swr/useMutateAddToCartSwr.ts`, `modules/api/graphql/queries/query-course.ts`,
|
|
9
|
+
`eslint.config.mjs`.
|
|
10
|
+
|
|
11
|
+
## FE-COMMENT-1 — Every export carries a docblock
|
|
12
|
+
|
|
13
|
+
415 of 417 non-spec component files contain a `/** */` block; lint `require-export-jsdoc` enforces
|
|
14
|
+
it.
|
|
15
|
+
|
|
16
|
+
| Case | When | Write |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Case 1 | A component | `/** Render the connected cart route. */` · `/** Draw every AI-owner state from resolved fixture data; no transport or translation lives here. */` |
|
|
19
|
+
| Case 2 | A props type | `/** Complete state/data/action contract for the pure advisor surface. */` · `/** Closed leaf props for a button-treatment sample. */` |
|
|
20
|
+
| Case 3 | A class-name export | `/** Messenger-like visual roles owned by the StarCi course-advisor surface. */` · `/** Grammar-owned page frame; this app alias adds no visual override. */` |
|
|
21
|
+
| Case 4 | A constant | `/** Canonical state inventory shared by connected owners and presentation tests. */` · `/** The key prefix, so a caller can revalidate every course read at once. */` |
|
|
22
|
+
| Case 5 | A route | `/** The routed authentication screen. The route mounts one page and makes no drawing decision. */` |
|
|
23
|
+
|
|
24
|
+
The one-liner states ownership or role ("owned by", "resolved by the connected owner", "no
|
|
25
|
+
transport lives here"), not the name.
|
|
26
|
+
|
|
27
|
+
## FE-COMMENT-2 — Fields are documented when the name is not enough
|
|
28
|
+
|
|
29
|
+
| Case | When | Write |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| Case 1 | A handler | `/** Called after the panel establishes a session. */ readonly signedIn?: () => void` |
|
|
32
|
+
| Case 2 | A prop whose source matters | `/** URL-owned journey rendered before the browser hydrates stored challenge metadata. */ readonly initialMode?: AuthMode` |
|
|
33
|
+
| Case 3 | A hook param | `/** The short human-facing identifier the route carries - \`fullstack-mastery\`, not a UUID. */ displayId?: string` |
|
|
34
|
+
| Case 4 | A Grammar prop | `/** Pending belongs to the action that started the work and blocks duplicate presses. */ readonly isPending?: boolean` |
|
|
35
|
+
| Case 5 | Self-evident fields | `readonly id: string`, `readonly body: string` carry nothing |
|
|
36
|
+
|
|
37
|
+
## FE-COMMENT-3 — Decision prose on hooks and documents
|
|
38
|
+
|
|
39
|
+
Hooks and GraphQL modules carry multi-paragraph docblocks whose paragraphs open with a
|
|
40
|
+
capitalised claim and then give the measured reason.
|
|
41
|
+
|
|
42
|
+
| Case | When | Write |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| Case 1 | A key decision | `THE DISPLAY ID IS PART OF THE KEY, which is what makes two course pages in one session safe: …` (`useQueryCourseSwr`) |
|
|
45
|
+
| Case 2 | A rejected alternative | `IT SENDS \`displayId\` AND NEVER \`id\`, and that is a measured decision rather than a preference. … The server answers that with \`success: false, error: COURSE_NOT_FOUND_EXCEPTION\`` |
|
|
46
|
+
| Case 3 | Why the key carries an argument | `THE KEY CARRIES THE COURSE, and it has to. Every hook sharing a key shares its state, so a grid of cards on one key is a grid where pressing ONE card puts every other card's control into the running state` (`useMutateAddToCartSwr`) |
|
|
47
|
+
| Case 4 | Selection rationale | `WHY THE CONTENT FACTS ARE SELECTED AND NOT ASKED FOR. …` (`query-course.ts`) |
|
|
48
|
+
| Case 5 | `@param` | `@param courseId - The course this hook's press is about, or \`undefined\` to stay idle.`; `@param input - {@link AuthenticationPageProps}` appears in one page and is not the norm |
|
|
49
|
+
|
|
50
|
+
## FE-COMMENT-4 — Line comments are sentences about why
|
|
51
|
+
|
|
52
|
+
201 `//` lines exist under `src/components` (non-spec). They sit above the statement and explain a
|
|
53
|
+
consequence; none restates the code.
|
|
54
|
+
|
|
55
|
+
| Case | When | Write |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Case 1 | A guard's reason | `// A server-rendered form has no React submit handler yet. Keeping its secret-bearing controls disabled until hydration prevents the browser's native GET fallback from placing an email, password or OTP in the URL when a reader acts before JavaScript attaches.` |
|
|
58
|
+
| Case 2 | Copy decision | `// The transport case deliberately does NOT say the details or the code were wrong, because nobody knows that - the request never got an answer.` |
|
|
59
|
+
| Case 3 | Cache decision | `// The sign-out drops every identity entry, whichever viewer it was cached under, and leaves every other cache namespace - and every non-tuple key - exactly where it was.` |
|
|
60
|
+
| Case 4 | Test stand-in | `// jsdom implements ranges but not their geometry, and the block asks a range where it is so the surface can be placed over it. A fixed rect is the honest stand-in: nothing here has layout.` |
|
|
61
|
+
| Case 5 | Config exception | the block comment above `files: ["src/modules/api/graphql/clients/options.ts"]` in `eslint.config.mjs` states why the exception lives in config and not inline |
|
|
62
|
+
|
|
63
|
+
## FE-COMMENT-5 — What a comment never contains
|
|
64
|
+
|
|
65
|
+
| Case | When | Write |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| Case 1 | A second language | English only in source (lint `no-second-language-in-source`, `no-second-language-in-path`); user copy lives in `src/messages/{en,vi}.json` |
|
|
68
|
+
| Case 2 | Emoji | none (lint `no-emoji-in-source`) |
|
|
69
|
+
| Case 3 | A lint suppression | `eslint-disable` in 0 files; the config carries the exception with its reason |
|
|
70
|
+
| Case 4 | A restated name | `/** Props for CartPage. */` alone is not observed; the docblock adds what the page owns: `/** The page owns only the route-level composition; CartBlock owns cart state and actions. */` |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Chú thích
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một khai báo hay câu lệnh frontend, nó có mang chú thích không,
|
|
4
|
+
và chú thích ấy nói gì?
|
|
5
|
+
|
|
6
|
+
Nguồn: `src/components/pages/AuthenticationPage/*`, `blocks/ai/StarCiAiChat/*`,
|
|
7
|
+
`blocks/ai/CourseAdvisorRecommendationCard/*`, `hooks/index.ts`, `hooks/swr/useQueryCourseSwr.ts`,
|
|
8
|
+
`hooks/swr/useMutateAddToCartSwr.ts`, `modules/api/graphql/queries/query-course.ts`,
|
|
9
|
+
`eslint.config.mjs`.
|
|
10
|
+
|
|
11
|
+
## FE-COMMENT-1 — Mọi export đều mang docblock
|
|
12
|
+
|
|
13
|
+
415 trên 417 tệp component không phải spec chứa một khối `/** */`; lint `require-export-jsdoc`
|
|
14
|
+
ép điều này.
|
|
15
|
+
|
|
16
|
+
| Case | Dùng khi | Viết |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Case 1 | Một component | `/** Render the connected cart route. */` · `/** Draw every AI-owner state from resolved fixture data; no transport or translation lives here. */` |
|
|
19
|
+
| Case 2 | Một kiểu props | `/** Complete state/data/action contract for the pure advisor surface. */` · `/** Closed leaf props for a button-treatment sample. */` |
|
|
20
|
+
| Case 3 | Một export class-name | `/** Messenger-like visual roles owned by the StarCi course-advisor surface. */` · `/** Grammar-owned page frame; this app alias adds no visual override. */` |
|
|
21
|
+
| Case 4 | Một hằng | `/** Canonical state inventory shared by connected owners and presentation tests. */` · `/** The key prefix, so a caller can revalidate every course read at once. */` |
|
|
22
|
+
| Case 5 | Một route | `/** The routed authentication screen. The route mounts one page and makes no drawing decision. */` |
|
|
23
|
+
|
|
24
|
+
Câu một dòng nêu quyền sở hữu hoặc vai trò ("owned by", "resolved by the connected owner", "no
|
|
25
|
+
transport lives here"), không nêu lại tên.
|
|
26
|
+
|
|
27
|
+
## FE-COMMENT-2 — Trường được chú thích khi tên chưa đủ nói
|
|
28
|
+
|
|
29
|
+
| Case | Dùng khi | Viết |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| Case 1 | Một bộ xử lý | `/** Called after the panel establishes a session. */ readonly signedIn?: () => void` |
|
|
32
|
+
| Case 2 | Một prop mà nguồn gốc quan trọng | `/** URL-owned journey rendered before the browser hydrates stored challenge metadata. */ readonly initialMode?: AuthMode` |
|
|
33
|
+
| Case 3 | Một tham số hook | `/** The short human-facing identifier the route carries - \`fullstack-mastery\`, not a UUID. */ displayId?: string` |
|
|
34
|
+
| Case 4 | Một prop Grammar | `/** Pending belongs to the action that started the work and blocks duplicate presses. */ readonly isPending?: boolean` |
|
|
35
|
+
| Case 5 | Trường tự hiển nhiên | `readonly id: string`, `readonly body: string` không mang gì |
|
|
36
|
+
|
|
37
|
+
## FE-COMMENT-3 — Văn xuôi lý giải quyết định trên hook và tài liệu
|
|
38
|
+
|
|
39
|
+
Hook và module GraphQL mang docblock nhiều đoạn; mỗi đoạn mở bằng một khẳng định viết hoa rồi đưa
|
|
40
|
+
ra lý do đã đo được.
|
|
41
|
+
|
|
42
|
+
| Case | Dùng khi | Viết |
|
|
43
|
+
| --- | --- | --- |
|
|
44
|
+
| Case 1 | Quyết định về khóa | `THE DISPLAY ID IS PART OF THE KEY, which is what makes two course pages in one session safe: …` (`useQueryCourseSwr`) |
|
|
45
|
+
| Case 2 | Phương án đã bác | `IT SENDS \`displayId\` AND NEVER \`id\`, and that is a measured decision rather than a preference. … The server answers that with \`success: false, error: COURSE_NOT_FOUND_EXCEPTION\`` |
|
|
46
|
+
| Case 3 | Vì sao khóa mang đối số | `THE KEY CARRIES THE COURSE, and it has to. Every hook sharing a key shares its state, so a grid of cards on one key is a grid where pressing ONE card puts every other card's control into the running state` (`useMutateAddToCartSwr`) |
|
|
47
|
+
| Case 4 | Lý do chọn trường | `WHY THE CONTENT FACTS ARE SELECTED AND NOT ASKED FOR. …` (`query-course.ts`) |
|
|
48
|
+
| Case 5 | `@param` | `@param courseId - The course this hook's press is about, or \`undefined\` to stay idle.`; `@param input - {@link AuthenticationPageProps}` xuất hiện ở một page và không phải chuẩn chung |
|
|
49
|
+
|
|
50
|
+
## FE-COMMENT-4 — Chú thích dòng là câu văn về lý do
|
|
51
|
+
|
|
52
|
+
201 dòng `//` tồn tại dưới `src/components` (không kể spec). Chúng đứng trên câu lệnh và giải
|
|
53
|
+
thích hệ quả; không dòng nào nhắc lại mã.
|
|
54
|
+
|
|
55
|
+
| Case | Dùng khi | Viết |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Case 1 | Lý do của một chốt chặn | `// A server-rendered form has no React submit handler yet. Keeping its secret-bearing controls disabled until hydration prevents the browser's native GET fallback from placing an email, password or OTP in the URL when a reader acts before JavaScript attaches.` |
|
|
58
|
+
| Case 2 | Quyết định câu chữ | `// The transport case deliberately does NOT say the details or the code were wrong, because nobody knows that - the request never got an answer.` |
|
|
59
|
+
| Case 3 | Quyết định cache | `// The sign-out drops every identity entry, whichever viewer it was cached under, and leaves every other cache namespace - and every non-tuple key - exactly where it was.` |
|
|
60
|
+
| Case 4 | Vật thế trong kiểm thử | `// jsdom implements ranges but not their geometry, and the block asks a range where it is so the surface can be placed over it. A fixed rect is the honest stand-in: nothing here has layout.` |
|
|
61
|
+
| Case 5 | Ngoại lệ cấu hình | khối chú thích trên `files: ["src/modules/api/graphql/clients/options.ts"]` trong `eslint.config.mjs` nêu vì sao ngoại lệ sống trong cấu hình chứ không nội tuyến |
|
|
62
|
+
|
|
63
|
+
## FE-COMMENT-5 — Thứ một chú thích không bao giờ chứa
|
|
64
|
+
|
|
65
|
+
| Case | Dùng khi | Viết |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| Case 1 | Ngôn ngữ thứ hai | chỉ tiếng Anh trong mã nguồn (lint `no-second-language-in-source`, `no-second-language-in-path`); câu chữ cho người dùng nằm trong `src/messages/{en,vi}.json` |
|
|
68
|
+
| Case 2 | Emoji | không có (lint `no-emoji-in-source`) |
|
|
69
|
+
| Case 3 | Tắt lint | `eslint-disable` trong 0 tệp; cấu hình mang ngoại lệ kèm lý do |
|
|
70
|
+
| Case 4 | Nhắc lại tên | không thấy `/** Props for CartPage. */` đứng một mình; docblock bổ sung page sở hữu gì: `/** The page owns only the route-level composition; CartBlock owns cart state and actions. */` |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Error
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a failure in the frontend, how is it represented and where
|
|
4
|
+
does it go?
|
|
5
|
+
|
|
6
|
+
Sources: `src/components/blocks/**/component.tsx`, `blocks/commerce/CartBlock/index.spec.tsx`,
|
|
7
|
+
`src/modules/toast/api.ts`, `src/modules/api/graphql/types.ts`, `src/hooks/swr/*`,
|
|
8
|
+
`packages/grammar/src/common/conformance.ts`.
|
|
9
|
+
|
|
10
|
+
## FE-ERROR-1 — Failure is a state of the pure half
|
|
11
|
+
|
|
12
|
+
| Case | When | Write |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Case 1 | The generic case | `"failed"` is a member of the block state union in 219 `component.tsx` files; the connected half maps SWR `error` to it and the pure half draws it |
|
|
15
|
+
| Case 2 | Several failure origins | `"sessionsFailed" \| "historyFailed" \| "streamFailed" \| "quotaRejected"` in `StarCiAiChatState`; `const stateNeedsRetry = (state) => state === "sessionsFailed" \|\| state === "historyFailed" \|\| state === "streamFailed" \|\| state === "quotaRejected"` |
|
|
16
|
+
| Case 3 | Failure copy | resolved by the connected half into `labels.states[props.state]` and drawn as an assistant turn: `{ id: \`state-${props.state}\`, role: "assistant", body: labels.states[props.state] }` |
|
|
17
|
+
| Case 4 | Retry | an action on `on`: `<Button variant="primary" size="sm" onPress={props.on?.retry}>{labels.retry}</Button>` |
|
|
18
|
+
| Case 5 | Connected spec | asserts the mapping: `mocks.cart.isLoading = true … expect(mocks.input?.blockState).toBe("pending")` |
|
|
19
|
+
|
|
20
|
+
## FE-ERROR-2 — The GraphQL envelope
|
|
21
|
+
|
|
22
|
+
| Case | When | Write |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Case 1 | Every response | `{ success, message, error, data }` selected in each document (`query Course … { success message error data { … } }`) and typed `GraphQLResponse<T>` in `modules/api/graphql/types.ts` |
|
|
25
|
+
| Case 2 | A read hook unwraps | `return result.data?.course?.data ?? null` — a missing payload becomes `null` data, not a throw |
|
|
26
|
+
| Case 3 | A write with feedback | `runGraphQLWithToast(action, options): Promise<boolean>` — "Execute one GraphQL write and surface both transport and typed-envelope failures" |
|
|
27
|
+
| Case 4 | Localised default copy | `const DEFAULT_MESSAGES: ToastMessages = { successTitle: "Success", errorTitle: "Error", unauthorizedTitle: "Unauthorized", … }` overridable per call through `messages` |
|
|
28
|
+
|
|
29
|
+
## FE-ERROR-3 — `throw new Error` where silence would be wrong
|
|
30
|
+
|
|
31
|
+
52 `throw new Error(…)` sites exist in `src/` (non-spec). They mark a result that the code cannot
|
|
32
|
+
continue without.
|
|
33
|
+
|
|
34
|
+
| Case | When | Write |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Case 1 | Empty payload on a required read | `if (value === undefined) throw new Error("Course Community returned no data")` |
|
|
37
|
+
| Case 2 | Mutation returned nothing | `if (result === null) throw new Error("Flashcard review completion returned no result")` |
|
|
38
|
+
| Case 3 | Slot used outside its owner | `if (value === undefined) throw new Error("CourseFoundationCategoryBlock slots must be rendered inside CourseFoundationCategoryBlockBase")` |
|
|
39
|
+
| Case 4 | Offline | `throw new Error("offline")` inside a fetcher so SWR records `error` |
|
|
40
|
+
| Case 5 | Custom class | one exists: `class PersonalProjectEnrollmentDeniedError extends Error` in `useQueryPersonalProjectTaskWorkspaceSwr.ts`; not a pattern |
|
|
41
|
+
|
|
42
|
+
## FE-ERROR-4 — Propagation
|
|
43
|
+
|
|
44
|
+
| Case | When | Write |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| Case 1 | Inside a hook | not caught; SWR stores it and the connected half reads `query.error` (`catch (` appears in 3 files across `src/`) |
|
|
47
|
+
| Case 2 | Inside a write action | `runGraphQLWithToast` returns `false` and toasts; the caller flips a local state (`payment.hasFailed`) |
|
|
48
|
+
| Case 3 | Render crash | `src/app/global-error.tsx` |
|
|
49
|
+
| Case 4 | Telemetry | `src/config/sentry.ts`, `src/instrumentation.ts`, `instrumentation-client.ts` |
|
|
50
|
+
|
|
51
|
+
## FE-ERROR-5 — Grammar package
|
|
52
|
+
|
|
53
|
+
| Case | When | Write |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Case 1 | Invalid definition at module init | `throw new TypeError(\`Invalid ${definition.familyId} Grammar conformance: missing=[…], unknown=[…]\`)` in `defineGrammarRuleConformance` |
|
|
56
|
+
| Case 2 | Invalid vocabulary value | `assertPresentationState("unknown")` throws `TypeError` (`common/index.test.mjs`) |
|
|
57
|
+
|
|
58
|
+
## Open question
|
|
59
|
+
|
|
60
|
+
Whether a typed error class should replace `throw new Error("…")` for the "no data" cases: one
|
|
61
|
+
custom class against 52 plain throws, so the plain form is what the code does today.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Lỗi
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một thất bại trong frontend, nó được biểu diễn ra sao và đi về
|
|
4
|
+
đâu?
|
|
5
|
+
|
|
6
|
+
Nguồn: `src/components/blocks/**/component.tsx`, `blocks/commerce/CartBlock/index.spec.tsx`,
|
|
7
|
+
`src/modules/toast/api.ts`, `src/modules/api/graphql/types.ts`, `src/hooks/swr/*`,
|
|
8
|
+
`packages/grammar/src/common/conformance.ts`.
|
|
9
|
+
|
|
10
|
+
## FE-ERROR-1 — Thất bại là một trạng thái của nửa thuần
|
|
11
|
+
|
|
12
|
+
| Case | Dùng khi | Viết |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Case 1 | Trường hợp chung | `"failed"` là một thành viên của union trạng thái block trong 219 tệp `component.tsx`; nửa nối ánh xạ `error` của SWR sang nó và nửa thuần vẽ nó |
|
|
15
|
+
| Case 2 | Nhiều nguồn thất bại | `"sessionsFailed" \| "historyFailed" \| "streamFailed" \| "quotaRejected"` trong `StarCiAiChatState`; `const stateNeedsRetry = (state) => state === "sessionsFailed" \|\| state === "historyFailed" \|\| state === "streamFailed" \|\| state === "quotaRejected"` |
|
|
16
|
+
| Case 3 | Câu chữ thất bại | được nửa nối giải vào `labels.states[props.state]` và vẽ như một lượt trợ lý: `{ id: \`state-${props.state}\`, role: "assistant", body: labels.states[props.state] }` |
|
|
17
|
+
| Case 4 | Thử lại | một hành động trên `on`: `<Button variant="primary" size="sm" onPress={props.on?.retry}>{labels.retry}</Button>` |
|
|
18
|
+
| Case 5 | Spec nửa nối | khẳng định phép ánh xạ: `mocks.cart.isLoading = true … expect(mocks.input?.blockState).toBe("pending")` |
|
|
19
|
+
|
|
20
|
+
## FE-ERROR-2 — Phong bì GraphQL
|
|
21
|
+
|
|
22
|
+
| Case | Dùng khi | Viết |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Case 1 | Mọi phản hồi | `{ success, message, error, data }` được chọn trong từng tài liệu (`query Course … { success message error data { … } }`) và định kiểu `GraphQLResponse<T>` trong `modules/api/graphql/types.ts` |
|
|
25
|
+
| Case 2 | Hook đọc bóc phong bì | `return result.data?.course?.data ?? null` — thiếu payload thành dữ liệu `null`, không ném |
|
|
26
|
+
| Case 3 | Ghi có phản hồi | `runGraphQLWithToast(action, options): Promise<boolean>` — thực thi một lệnh ghi GraphQL và làm nổi cả thất bại vận chuyển lẫn thất bại trong phong bì có kiểu |
|
|
27
|
+
| Case 4 | Câu chữ mặc định có bản địa hóa | `const DEFAULT_MESSAGES: ToastMessages = { successTitle: "Success", errorTitle: "Error", unauthorizedTitle: "Unauthorized", … }` ghi đè theo từng lần gọi qua `messages` |
|
|
28
|
+
|
|
29
|
+
## FE-ERROR-3 — `throw new Error` nơi im lặng sẽ là sai
|
|
30
|
+
|
|
31
|
+
52 chỗ `throw new Error(…)` tồn tại trong `src/` (không kể spec). Chúng đánh dấu một kết quả mà mã
|
|
32
|
+
không thể tiếp tục nếu thiếu.
|
|
33
|
+
|
|
34
|
+
| Case | Dùng khi | Viết |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Case 1 | Payload rỗng ở lượt đọc bắt buộc | `if (value === undefined) throw new Error("Course Community returned no data")` |
|
|
37
|
+
| Case 2 | Mutation không trả gì | `if (result === null) throw new Error("Flashcard review completion returned no result")` |
|
|
38
|
+
| Case 3 | Khe dùng ngoài chủ sở hữu | `if (value === undefined) throw new Error("CourseFoundationCategoryBlock slots must be rendered inside CourseFoundationCategoryBlockBase")` |
|
|
39
|
+
| Case 4 | Ngoại tuyến | `throw new Error("offline")` bên trong một fetcher để SWR ghi nhận `error` |
|
|
40
|
+
| Case 5 | Lớp tùy chỉnh | có đúng một: `class PersonalProjectEnrollmentDeniedError extends Error` trong `useQueryPersonalProjectTaskWorkspaceSwr.ts`; không phải mẫu |
|
|
41
|
+
|
|
42
|
+
## FE-ERROR-4 — Lan truyền
|
|
43
|
+
|
|
44
|
+
| Case | Dùng khi | Viết |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| Case 1 | Bên trong hook | không bắt; SWR lưu nó và nửa nối đọc `query.error` (`catch (` xuất hiện trong 3 tệp toàn `src/`) |
|
|
47
|
+
| Case 2 | Bên trong hành động ghi | `runGraphQLWithToast` trả `false` và hiện toast; bên gọi lật một state cục bộ (`payment.hasFailed`) |
|
|
48
|
+
| Case 3 | Sập khi vẽ | `src/app/global-error.tsx` |
|
|
49
|
+
| Case 4 | Đo xa | `src/config/sentry.ts`, `src/instrumentation.ts`, `instrumentation-client.ts` |
|
|
50
|
+
|
|
51
|
+
## FE-ERROR-5 — Gói Grammar
|
|
52
|
+
|
|
53
|
+
| Case | Dùng khi | Viết |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Case 1 | Định nghĩa không hợp lệ lúc khởi tạo module | `throw new TypeError(\`Invalid ${definition.familyId} Grammar conformance: missing=[…], unknown=[…]\`)` trong `defineGrammarRuleConformance` |
|
|
56
|
+
| Case 2 | Giá trị từ vựng không hợp lệ | `assertPresentationState("unknown")` ném `TypeError` (`common/index.test.mjs`) |
|
|
57
|
+
|
|
58
|
+
## Câu hỏi để ngỏ
|
|
59
|
+
|
|
60
|
+
Có nên thay `throw new Error("…")` bằng một lớp lỗi có kiểu cho các trường hợp "không có dữ liệu"
|
|
61
|
+
hay không: một lớp tùy chỉnh so với 52 lần ném trần, nên dạng trần là điều mã đang làm hôm nay.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Folder
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a piece of frontend code, which directory and which file
|
|
4
|
+
name does it get?
|
|
5
|
+
|
|
6
|
+
Sources: `starci-academy-fe/src/components/**`, `src/hooks/**`, `src/modules/api/**`, `src/app/**`,
|
|
7
|
+
`packages/grammar/src/**`. Counts are file counts at the time of reading.
|
|
8
|
+
|
|
9
|
+
## FE-FOLDER-1 — Tier directories
|
|
10
|
+
|
|
11
|
+
`src/components/` holds eight tier folders. Blocks are grouped by product domain; pages and leaves
|
|
12
|
+
are flat.
|
|
13
|
+
|
|
14
|
+
| Case | When | Write |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| Case 1 | A reusable fixed visual unit | `src/components/leaves/<Name>/` (40 units, flat) |
|
|
17
|
+
| Case 2 | A feature unit with a connected half | `src/components/blocks/<domain>/<Name>/` (109 units under 11 domains: `ai auth coding commerce community courses dashboard learn locale profile search`) |
|
|
18
|
+
| Case 3 | A routed screen | `src/components/pages/<Name>Page/` (50 units, flat) |
|
|
19
|
+
| Case 4 | Other tiers present | `branches/`, `composites/`, `layouts/`, `overlays/`, `product-shells/` |
|
|
20
|
+
|
|
21
|
+
## FE-FOLDER-2 — The file set of one unit
|
|
22
|
+
|
|
23
|
+
One unit is one PascalCase folder. Its files carry fixed names; the unit name is the folder, never
|
|
24
|
+
the file.
|
|
25
|
+
|
|
26
|
+
| Case | When | Write |
|
|
27
|
+
| --- | --- | --- |
|
|
28
|
+
| Case 1 | Page unit (`pages/AuthenticationPage/`) | `classNames.ts` · `component.tsx` · `index.tsx` |
|
|
29
|
+
| Case 2 | Page unit with specs (`pages/CartPage/`) | `component.tsx` · `index.spec.tsx` · `index.tsx` |
|
|
30
|
+
| Case 3 | Block unit (`blocks/ai/StarCiAiChat/`) | `classNames.ts` · `component.spec.tsx` · `component.tsx` · `index.spec.tsx` · `index.tsx` |
|
|
31
|
+
| Case 4 | Leaf unit (`leaves/ButtonStateSample/`) | `classNames.ts` · `index.tsx` — a leaf has no `component.tsx`, because it has no connected half |
|
|
32
|
+
|
|
33
|
+
Counts across pages (50): `index.tsx` 49, `component.tsx` 49, `component.spec.tsx` 42,
|
|
34
|
+
`index.spec.tsx` 24, `audit.md` 22, `classNames.ts` 9. Across blocks (109): `component.tsx` 101,
|
|
35
|
+
`index.tsx` 95, `classNames.ts` 76, `index.spec.tsx` 61, `component.spec.tsx` 55. Across leaves
|
|
36
|
+
(40): `index.tsx` 40, `classNames.ts` 33, `index.spec.tsx` 19.
|
|
37
|
+
|
|
38
|
+
`classNames.ts` is present only when the unit owns class strings. A page that composes a block
|
|
39
|
+
into a Grammar frame usually has none; the 9 pages that have one alias a Grammar page frame:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// pages/AuthenticationPage/classNames.ts
|
|
43
|
+
import { cn } from "@heroui/react"
|
|
44
|
+
import { formPageClassName } from "@starci/grammar/common"
|
|
45
|
+
|
|
46
|
+
/** Grammar-owned page frame; this app alias adds no visual override. */
|
|
47
|
+
export const authenticationPageClassName = cn(formPageClassName)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## FE-FOLDER-3 — Route files mount one page
|
|
51
|
+
|
|
52
|
+
`src/app/[lang]/**/page.tsx` is the only place a default export exists (67 in `src/app`, 0 in
|
|
53
|
+
`src/components`).
|
|
54
|
+
|
|
55
|
+
| Case | When | Write |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Case 1 | A route | `app/[lang]/authentication/page.tsx`: `import { AuthenticationPage } from "@/components/pages/AuthenticationPage"` … `const AuthenticationRoute = () => <AuthenticationPage {...{}} />` … `export default AuthenticationRoute` |
|
|
58
|
+
| Case 2 | Global app files | `app/globals.css`, `app/providers.tsx`, `app/global-error.tsx`, `app/sitemap.ts`, `app/robots.ts`, each with a sibling `.spec` |
|
|
59
|
+
|
|
60
|
+
## FE-FOLDER-4 — Data layer placement
|
|
61
|
+
|
|
62
|
+
| Case | When | Write |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| Case 1 | A read hook | `src/hooks/swr/useQuery<Thing>Swr.ts` (80) with sibling `.spec.ts` |
|
|
65
|
+
| Case 2 | A write hook | `src/hooks/swr/useMutate<Thing>Swr.ts` (32) with sibling `.spec.ts` |
|
|
66
|
+
| Case 3 | Auth / socket hooks | `src/hooks/auth/useSessionToken.ts`, `src/hooks/socketio/**` |
|
|
67
|
+
| Case 4 | The one door for components | `src/hooks/index.ts` re-exports hooks only ("Hooks ONLY. Types, cache keys and query modules stay behind their own paths") |
|
|
68
|
+
| Case 5 | A GraphQL document | `src/modules/api/graphql/queries/query-<thing>.ts`, `src/modules/api/graphql/mutations/mutation-<thing>.ts` |
|
|
69
|
+
| Case 6 | Response/request types | `src/modules/api/graphql/queries/types/<thing>.ts` |
|
|
70
|
+
| Case 7 | Non-API modules | `src/modules/{ai,code,learn,routing,search,theme,toast,types,utils}/` |
|
|
71
|
+
|
|
72
|
+
## FE-FOLDER-5 — Grammar package unit
|
|
73
|
+
|
|
74
|
+
| Case | When | Write |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| Case 1 | A Grammar component | `packages/grammar/src/core/<primitive|composite|branch|composition>/<Name>/index.tsx` (39 `index.tsx`) |
|
|
77
|
+
| Case 2 | Its class strings | sibling `classNames.ts` (13), e.g. `core/branch/Rail/classNames.ts` |
|
|
78
|
+
| Case 3 | Its spec | sibling `index.spec.tsx` (24); CSS proof in `styles.spec.ts` (6) |
|
|
79
|
+
| Case 4 | Family entry | `core/index.ts`, `core/styles.css`, `core/dna.ts`; `common/index.ts` re-exports `state.js`, `spacing.js`, `conformance.js`, `renderers.js`, `registry.js` |
|
|
80
|
+
| Case 5 | Built-output proof | `common/index.test.mjs`, `core/index.test.mjs`, `package-boundary.test.mjs` (node:test against `dist/`) |
|
|
81
|
+
|
|
82
|
+
## FE-FOLDER-6 — What a unit folder does not hold
|
|
83
|
+
|
|
84
|
+
| Case | When | Write |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| Case 1 | A second component in the same folder | Not observed in 87 of 109 block folders; the unit is one export per `index.tsx`/`component.tsx` |
|
|
87
|
+
| Case 2 | Class strings in `component.tsx` | Never; they go to `classNames.ts` (lint `class-names-in-colocated-file`, `no-inline-class-name`) |
|
|
88
|
+
| Case 3 | A `__tests__/` folder | 0 across `src/`; specs sit beside their file |
|
|
89
|
+
| Case 4 | A `helpers/` or `utils/` folder | 0 under `src/components` (lint `no-helper-folder-in-components`) |
|
|
90
|
+
|
|
91
|
+
## Open question — extra files inside block folders
|
|
92
|
+
|
|
93
|
+
22 files under `src/components/blocks` fall outside the five canonical names: the
|
|
94
|
+
`blocks/profile/overview/` sub-tree (`OverviewCourses.tsx`, `SkillSnapshot.tsx`, `shared.ts`,
|
|
95
|
+
`useOverviewEvidence.ts`, …), `learn/CourseMockInterviewResultBlock/verdict.ts`,
|
|
96
|
+
`profile/ProfileCvBuilder/buildCvTexSource.ts`, and three `classNames.spec.ts`/`styles.spec.ts`.
|
|
97
|
+
No dominant convention exists for where a block-private helper module goes, so this file does not
|
|
98
|
+
legislate one.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Thư mục
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một mẩu mã frontend, nó nằm ở thư mục nào và mang tên tệp gì?
|
|
4
|
+
|
|
5
|
+
Nguồn: `starci-academy-fe/src/components/**`, `src/hooks/**`, `src/modules/api/**`, `src/app/**`,
|
|
6
|
+
`packages/grammar/src/**`. Các con số là số tệp tại thời điểm đọc.
|
|
7
|
+
|
|
8
|
+
## FE-FOLDER-1 — Thư mục theo tầng
|
|
9
|
+
|
|
10
|
+
`src/components/` chứa tám thư mục tầng. Block được gom theo miền sản phẩm; page và leaf nằm phẳng.
|
|
11
|
+
|
|
12
|
+
| Case | Dùng khi | Viết |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Case 1 | Một đơn vị hình ảnh cố định, tái dùng | `src/components/leaves/<Name>/` (40 đơn vị, phẳng) |
|
|
15
|
+
| Case 2 | Một đơn vị tính năng có nửa nối dữ liệu | `src/components/blocks/<domain>/<Name>/` (109 đơn vị trong 11 miền: `ai auth coding commerce community courses dashboard learn locale profile search`) |
|
|
16
|
+
| Case 3 | Một màn hình có route | `src/components/pages/<Name>Page/` (50 đơn vị, phẳng) |
|
|
17
|
+
| Case 4 | Các tầng khác đang có | `branches/`, `composites/`, `layouts/`, `overlays/`, `product-shells/` |
|
|
18
|
+
|
|
19
|
+
## FE-FOLDER-2 — Bộ tệp của một đơn vị
|
|
20
|
+
|
|
21
|
+
Một đơn vị là một thư mục PascalCase. Các tệp bên trong mang tên cố định; tên đơn vị là tên thư
|
|
22
|
+
mục, không bao giờ là tên tệp.
|
|
23
|
+
|
|
24
|
+
| Case | Dùng khi | Viết |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Đơn vị page (`pages/AuthenticationPage/`) | `classNames.ts` · `component.tsx` · `index.tsx` |
|
|
27
|
+
| Case 2 | Đơn vị page có spec (`pages/CartPage/`) | `component.tsx` · `index.spec.tsx` · `index.tsx` |
|
|
28
|
+
| Case 3 | Đơn vị block (`blocks/ai/StarCiAiChat/`) | `classNames.ts` · `component.spec.tsx` · `component.tsx` · `index.spec.tsx` · `index.tsx` |
|
|
29
|
+
| Case 4 | Đơn vị leaf (`leaves/ButtonStateSample/`) | `classNames.ts` · `index.tsx` — leaf không có `component.tsx`, vì nó không có nửa nối dữ liệu |
|
|
30
|
+
|
|
31
|
+
Số đếm trong pages (50): `index.tsx` 49, `component.tsx` 49, `component.spec.tsx` 42,
|
|
32
|
+
`index.spec.tsx` 24, `audit.md` 22, `classNames.ts` 9. Trong blocks (109): `component.tsx` 101,
|
|
33
|
+
`index.tsx` 95, `classNames.ts` 76, `index.spec.tsx` 61, `component.spec.tsx` 55. Trong leaves
|
|
34
|
+
(40): `index.tsx` 40, `classNames.ts` 33, `index.spec.tsx` 19.
|
|
35
|
+
|
|
36
|
+
`classNames.ts` chỉ có mặt khi đơn vị sở hữu chuỗi class. Một page chỉ ghép block vào khung
|
|
37
|
+
Grammar thường không có tệp này; 9 page có tệp này đều chỉ đặt bí danh cho khung trang của Grammar:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
// pages/AuthenticationPage/classNames.ts
|
|
41
|
+
import { cn } from "@heroui/react"
|
|
42
|
+
import { formPageClassName } from "@starci/grammar/common"
|
|
43
|
+
|
|
44
|
+
/** Grammar-owned page frame; this app alias adds no visual override. */
|
|
45
|
+
export const authenticationPageClassName = cn(formPageClassName)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## FE-FOLDER-3 — Tệp route gắn đúng một page
|
|
49
|
+
|
|
50
|
+
`src/app/[lang]/**/page.tsx` là nơi duy nhất có default export (67 trong `src/app`, 0 trong
|
|
51
|
+
`src/components`).
|
|
52
|
+
|
|
53
|
+
| Case | Dùng khi | Viết |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Case 1 | Một route | `app/[lang]/authentication/page.tsx`: `import { AuthenticationPage } from "@/components/pages/AuthenticationPage"` … `const AuthenticationRoute = () => <AuthenticationPage {...{}} />` … `export default AuthenticationRoute` |
|
|
56
|
+
| Case 2 | Các tệp toàn cục của app | `app/globals.css`, `app/providers.tsx`, `app/global-error.tsx`, `app/sitemap.ts`, `app/robots.ts`, mỗi tệp có `.spec` kề bên |
|
|
57
|
+
|
|
58
|
+
## FE-FOLDER-4 — Vị trí tầng dữ liệu
|
|
59
|
+
|
|
60
|
+
| Case | Dùng khi | Viết |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| Case 1 | Hook đọc | `src/hooks/swr/useQuery<Thing>Swr.ts` (80) kèm `.spec.ts` kề bên |
|
|
63
|
+
| Case 2 | Hook ghi | `src/hooks/swr/useMutate<Thing>Swr.ts` (32) kèm `.spec.ts` kề bên |
|
|
64
|
+
| Case 3 | Hook xác thực / socket | `src/hooks/auth/useSessionToken.ts`, `src/hooks/socketio/**` |
|
|
65
|
+
| Case 4 | Cánh cửa duy nhất cho component | `src/hooks/index.ts` chỉ re-export hook (docblock ghi rõ chỉ hook; kiểu, khóa cache và module truy vấn nằm sau đường dẫn riêng của chúng) |
|
|
66
|
+
| Case 5 | Một tài liệu GraphQL | `src/modules/api/graphql/queries/query-<thing>.ts`, `src/modules/api/graphql/mutations/mutation-<thing>.ts` |
|
|
67
|
+
| Case 6 | Kiểu request/response | `src/modules/api/graphql/queries/types/<thing>.ts` |
|
|
68
|
+
| Case 7 | Module ngoài API | `src/modules/{ai,code,learn,routing,search,theme,toast,types,utils}/` |
|
|
69
|
+
|
|
70
|
+
## FE-FOLDER-5 — Đơn vị trong gói Grammar
|
|
71
|
+
|
|
72
|
+
| Case | Dùng khi | Viết |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Case 1 | Một component Grammar | `packages/grammar/src/core/<primitive|composite|branch|composition>/<Name>/index.tsx` (39 `index.tsx`) |
|
|
75
|
+
| Case 2 | Chuỗi class của nó | `classNames.ts` kề bên (13), ví dụ `core/branch/Rail/classNames.ts` |
|
|
76
|
+
| Case 3 | Spec của nó | `index.spec.tsx` kề bên (24); chứng minh CSS trong `styles.spec.ts` (6) |
|
|
77
|
+
| Case 4 | Cửa vào của họ | `core/index.ts`, `core/styles.css`, `core/dna.ts`; `common/index.ts` re-export `state.js`, `spacing.js`, `conformance.js`, `renderers.js`, `registry.js` |
|
|
78
|
+
| Case 5 | Chứng minh bản build | `common/index.test.mjs`, `core/index.test.mjs`, `package-boundary.test.mjs` (node:test chạy trên `dist/`) |
|
|
79
|
+
|
|
80
|
+
## FE-FOLDER-6 — Thứ một thư mục đơn vị không chứa
|
|
81
|
+
|
|
82
|
+
| Case | Dùng khi | Viết |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| Case 1 | Một component thứ hai trong cùng thư mục | Không thấy ở 87 trên 109 thư mục block; đơn vị là một export cho mỗi `index.tsx`/`component.tsx` |
|
|
85
|
+
| Case 2 | Chuỗi class trong `component.tsx` | Không bao giờ; chúng đi vào `classNames.ts` (lint `class-names-in-colocated-file`, `no-inline-class-name`) |
|
|
86
|
+
| Case 3 | Thư mục `__tests__/` | 0 trong toàn bộ `src/`; spec nằm cạnh tệp của nó |
|
|
87
|
+
| Case 4 | Thư mục `helpers/` hay `utils/` | 0 dưới `src/components` (lint `no-helper-folder-in-components`) |
|
|
88
|
+
|
|
89
|
+
## Câu hỏi để ngỏ — tệp phụ trong thư mục block
|
|
90
|
+
|
|
91
|
+
22 tệp dưới `src/components/blocks` nằm ngoài năm tên chuẩn: cây con `blocks/profile/overview/`
|
|
92
|
+
(`OverviewCourses.tsx`, `SkillSnapshot.tsx`, `shared.ts`, `useOverviewEvidence.ts`, …),
|
|
93
|
+
`learn/CourseMockInterviewResultBlock/verdict.ts`, `profile/ProfileCvBuilder/buildCvTexSource.ts`,
|
|
94
|
+
và ba `classNames.spec.ts`/`styles.spec.ts`. Chưa có quy ước chiếm ưu thế cho chỗ đặt một module
|
|
95
|
+
helper riêng của block, nên tệp này không áp đặt.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Function
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a frontend component, hook or helper, what shape does the
|
|
4
|
+
function take, what does it receive, and what does it return?
|
|
5
|
+
|
|
6
|
+
Sources: `src/components/pages/AuthenticationPage/*`, `pages/CartPage/index.tsx`,
|
|
7
|
+
`blocks/ai/StarCiAiChat/*`, `blocks/ai/CourseAdvisorRecommendationCard/*`,
|
|
8
|
+
`leaves/ButtonStateSample/index.tsx`, `hooks/swr/useQueryCourseSwr.ts`,
|
|
9
|
+
`hooks/swr/useMutateAddToCartSwr.ts`, `packages/grammar/src/core/primitive/Button/index.tsx`.
|
|
10
|
+
|
|
11
|
+
## FE-FUNCTION-1 — A component is an arrow const with one `props` parameter
|
|
12
|
+
|
|
13
|
+
| Case | When | Write |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Any app component | `export const AuthenticationPageBase = (props: AuthenticationPageProps) => { … }` (441 `export const X = (` against 0 `export function` under `src/components`; 428 take `(props: XProps)`, 0 destructure at the signature) |
|
|
16
|
+
| Case 2 | Reading fields | `const { on } = props` at the top, or `props.props.labels`, `props.state` inline |
|
|
17
|
+
| Case 3 | Expression body when there is no local state | `export const ButtonStateSample = (props: ButtonStateSampleProps) => ( <span …>{props.props.label}</span> )` |
|
|
18
|
+
| Case 4 | Grammar package component | destructures with defaults at the signature: `export const Button = ({ children, variant = "secondary", size = "md", type = "button", …, onPress }: ButtonProps) => …` — the package convention differs from the app convention; each side is internally consistent |
|
|
19
|
+
|
|
20
|
+
## FE-FUNCTION-2 — The three-part contract of a pure half
|
|
21
|
+
|
|
22
|
+
| Case | When | Write |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Case 1 | Full block contract | `export type StarCiAiChatProps = { readonly state: StarCiAiChatState; readonly props: StarCiAiChatData; readonly on?: StarCiAiChatActions }` (`state`/`blockState` in 85, `props:` in 78, `on?:` in 69 of 101 block `component.tsx`) |
|
|
25
|
+
| Case 2 | Leaf contract | `export type ButtonStateSampleProps = { readonly props: ButtonStateSampleData; readonly isLoading?: boolean }` |
|
|
26
|
+
| Case 3 | Card with an action slot | `{ readonly props: CourseAdvisorRecommendationCardData; readonly isLoading?: boolean; readonly action: { readonly href: string; readonly label: string } }` |
|
|
27
|
+
| Case 4 | Page with only actions | `{ readonly measure?: "form" \| "formCompact"; readonly initialMode?: AuthMode; readonly initialStep?: "details" \| "code"; readonly on?: AuthenticationPageActions }` |
|
|
28
|
+
|
|
29
|
+
## FE-FUNCTION-3 — Where the props type goes
|
|
30
|
+
|
|
31
|
+
| Case | When | Write |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Case 1 | Pure half | Declared in `component.tsx` directly above the component, together with its `Data`, `Labels`, `Actions`, `State` parts |
|
|
34
|
+
| Case 2 | Connected half that takes input | Declared in `index.tsx`: `export type CourseAdvisorRecommendationCardProps = { readonly recommendation: CourseAdvisorRecommendation }` |
|
|
35
|
+
| Case 3 | Connected half that takes nothing | `export type CartPageProps = Record<never, never>` and the body starts with `void props` (59 index files; 34 use `Record<never, never>`) |
|
|
36
|
+
| Case 4 | Local sub-render | `type TurnProps = { readonly turn: StarCiAiTurn; … }` unexported, above `const Turn` |
|
|
37
|
+
|
|
38
|
+
## FE-FUNCTION-4 — What each half does
|
|
39
|
+
|
|
40
|
+
| Case | When | Write |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Case 1 | Connected half | `"use client"`; calls `useTranslations`, `useLocale`, `useRouter`, `useQuery…Swr`; resolves every label and fact; returns `<XBase state=… props={{ … }} on={{ … }} />` (49/49 page `index.tsx` and 89/109 block `index.tsx` carry `"use client"`) |
|
|
43
|
+
| Case 2 | Pure half | No `"use client"` (0/49 pages, 3/109 blocks); no runtime import from `@/hooks` or `@/modules/api` (0 of 150 `component.tsx`; 18 import a type only) |
|
|
44
|
+
| Case 3 | Route-derived input | `const routeState = (value: string \| null) => { switch (value) { case "sign-up": return { mode: "signUp" as const, step: "details" as const, measure: "form" as const } … } }` in `index.tsx`, then `const initial = routeState(authState)` |
|
|
45
|
+
| Case 4 | Navigation as an action | `on={{ signedIn: () => router.replace("/dashboard") }}` |
|
|
46
|
+
|
|
47
|
+
## FE-FUNCTION-5 — When a helper is extracted
|
|
48
|
+
|
|
49
|
+
| Case | When | Write |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Case 1 | A boolean over the state union | `const stateNeedsRetry = (state: StarCiAiChatState): boolean => state === "sessionsFailed" \|\| …` above the component (11 of 101 block `component.tsx` hold such consts) |
|
|
52
|
+
| Case 2 | A string builder | `const turnMarkdown = (turn: StarCiAiTurn, partialLabel: string): string => { … }` |
|
|
53
|
+
| Case 3 | A repeated JSX fragment | `const Turn = (props: TurnProps) => { … }` in the same file, not a new folder |
|
|
54
|
+
| Case 4 | A branch inside the render | an IIFE: `const transcript = (() => { if (…) return …; if (…) return …; return … })()` |
|
|
55
|
+
| Case 5 | A class variant | goes to `classNames.ts` as `getAiChatBubbleClassName(role)` |
|
|
56
|
+
|
|
57
|
+
## FE-FUNCTION-6 — Hook shape
|
|
58
|
+
|
|
59
|
+
| Case | When | Write |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| Case 1 | Read | `export const useQueryCourseSwr = ({ displayId }: UseQueryCourseSwrParams = {}) => { const viewer = useViewerKey(); return useSWR<CourseDetail \| null>(displayId === undefined ? null : [QUERY_COURSE_SWR_KEY, displayId, viewer ?? "guest"], async () => { const result = await queryCourse({ request: { displayId } }); return result.data?.course?.data ?? null }) }` |
|
|
62
|
+
| Case 2 | Write | `export const useMutateAddToCartSwr = (courseId?: string) => useSWRMutation(courseId === undefined ? null : [MUTATE_ADD_TO_CART_SWR_KEY, courseId], async (_key: readonly [string, string], { arg }: AddToCartTrigger) => mutationAddToCart({ courseId: arg.courseId }))` |
|
|
63
|
+
| Case 3 | Return | the SWR result is returned as-is; the component reads `query.data`, `query.isLoading`, `checkout.trigger`, `checkout.isMutating` |
|
|
64
|
+
| Case 4 | Key | `null` when the input is missing; the viewer identity is part of a read key |
|
|
65
|
+
|
|
66
|
+
## FE-FUNCTION-7 — Route file
|
|
67
|
+
|
|
68
|
+
| Case | When | Write |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Case 1 | Every `page.tsx` | `const AuthenticationRoute = () => <AuthenticationPage {...{}} />` then `export default AuthenticationRoute` — the route makes no drawing decision |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Hàm
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một component, hook hay helper frontend, hàm có hình dạng gì,
|
|
4
|
+
nhận gì, và trả về gì?
|
|
5
|
+
|
|
6
|
+
Nguồn: `src/components/pages/AuthenticationPage/*`, `pages/CartPage/index.tsx`,
|
|
7
|
+
`blocks/ai/StarCiAiChat/*`, `blocks/ai/CourseAdvisorRecommendationCard/*`,
|
|
8
|
+
`leaves/ButtonStateSample/index.tsx`, `hooks/swr/useQueryCourseSwr.ts`,
|
|
9
|
+
`hooks/swr/useMutateAddToCartSwr.ts`, `packages/grammar/src/core/primitive/Button/index.tsx`.
|
|
10
|
+
|
|
11
|
+
## FE-FUNCTION-1 — Component là một arrow const với một tham số `props`
|
|
12
|
+
|
|
13
|
+
| Case | Dùng khi | Viết |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Mọi component trong app | `export const AuthenticationPageBase = (props: AuthenticationPageProps) => { … }` (441 `export const X = (` so với 0 `export function` dưới `src/components`; 428 nhận `(props: XProps)`, 0 phá cấu trúc ngay tại chữ ký) |
|
|
16
|
+
| Case 2 | Đọc trường | `const { on } = props` ở đầu thân, hoặc `props.props.labels`, `props.state` ngay tại chỗ |
|
|
17
|
+
| Case 3 | Thân biểu thức khi không có state cục bộ | `export const ButtonStateSample = (props: ButtonStateSampleProps) => ( <span …>{props.props.label}</span> )` |
|
|
18
|
+
| Case 4 | Component trong gói Grammar | phá cấu trúc kèm mặc định tại chữ ký: `export const Button = ({ children, variant = "secondary", size = "md", type = "button", …, onPress }: ButtonProps) => …` — quy ước của gói khác quy ước của app; mỗi bên nhất quán nội bộ |
|
|
19
|
+
|
|
20
|
+
## FE-FUNCTION-2 — Hợp đồng ba phần của nửa thuần
|
|
21
|
+
|
|
22
|
+
| Case | Dùng khi | Viết |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Case 1 | Hợp đồng block đầy đủ | `export type StarCiAiChatProps = { readonly state: StarCiAiChatState; readonly props: StarCiAiChatData; readonly on?: StarCiAiChatActions }` (`state`/`blockState` ở 85, `props:` ở 78, `on?:` ở 69 trên 101 `component.tsx` của block) |
|
|
25
|
+
| Case 2 | Hợp đồng leaf | `export type ButtonStateSampleProps = { readonly props: ButtonStateSampleData; readonly isLoading?: boolean }` |
|
|
26
|
+
| Case 3 | Thẻ có khe hành động | `{ readonly props: CourseAdvisorRecommendationCardData; readonly isLoading?: boolean; readonly action: { readonly href: string; readonly label: string } }` |
|
|
27
|
+
| Case 4 | Page chỉ có hành động | `{ readonly measure?: "form" \| "formCompact"; readonly initialMode?: AuthMode; readonly initialStep?: "details" \| "code"; readonly on?: AuthenticationPageActions }` |
|
|
28
|
+
|
|
29
|
+
## FE-FUNCTION-3 — Kiểu props đặt ở đâu
|
|
30
|
+
|
|
31
|
+
| Case | Dùng khi | Viết |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| Case 1 | Nửa thuần | Khai báo trong `component.tsx` ngay trên component, cùng các phần `Data`, `Labels`, `Actions`, `State` |
|
|
34
|
+
| Case 2 | Nửa nối có đầu vào | Khai báo trong `index.tsx`: `export type CourseAdvisorRecommendationCardProps = { readonly recommendation: CourseAdvisorRecommendation }` |
|
|
35
|
+
| Case 3 | Nửa nối không nhận gì | `export type CartPageProps = Record<never, never>` và thân bắt đầu bằng `void props` (59 tệp index; 34 dùng `Record<never, never>`) |
|
|
36
|
+
| Case 4 | Bộ vẽ con cục bộ | `type TurnProps = { readonly turn: StarCiAiTurn; … }` không export, đặt trên `const Turn` |
|
|
37
|
+
|
|
38
|
+
## FE-FUNCTION-4 — Mỗi nửa làm gì
|
|
39
|
+
|
|
40
|
+
| Case | Dùng khi | Viết |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Case 1 | Nửa nối | `"use client"`; gọi `useTranslations`, `useLocale`, `useRouter`, `useQuery…Swr`; giải mọi nhãn và dữ kiện; trả về `<XBase state=… props={{ … }} on={{ … }} />` (49/49 `index.tsx` của page và 89/109 `index.tsx` của block mang `"use client"`) |
|
|
43
|
+
| Case 2 | Nửa thuần | Không `"use client"` (0/49 page, 3/109 block); không import lúc chạy từ `@/hooks` hay `@/modules/api` (0 trên 150 `component.tsx`; 18 tệp chỉ import kiểu) |
|
|
44
|
+
| Case 3 | Đầu vào suy từ route | `const routeState = (value: string \| null) => { switch (value) { case "sign-up": return { mode: "signUp" as const, step: "details" as const, measure: "form" as const } … } }` trong `index.tsx`, rồi `const initial = routeState(authState)` |
|
|
45
|
+
| Case 4 | Điều hướng là một hành động | `on={{ signedIn: () => router.replace("/dashboard") }}` |
|
|
46
|
+
|
|
47
|
+
## FE-FUNCTION-5 — Khi nào tách helper
|
|
48
|
+
|
|
49
|
+
| Case | Dùng khi | Viết |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Case 1 | Một boolean trên union trạng thái | `const stateNeedsRetry = (state: StarCiAiChatState): boolean => state === "sessionsFailed" \|\| …` đặt trên component (11 trên 101 `component.tsx` của block có const như vậy) |
|
|
52
|
+
| Case 2 | Một bộ dựng chuỗi | `const turnMarkdown = (turn: StarCiAiTurn, partialLabel: string): string => { … }` |
|
|
53
|
+
| Case 3 | Một mảnh JSX lặp lại | `const Turn = (props: TurnProps) => { … }` trong cùng tệp, không mở thư mục mới |
|
|
54
|
+
| Case 4 | Một nhánh bên trong phần vẽ | một IIFE: `const transcript = (() => { if (…) return …; if (…) return …; return … })()` |
|
|
55
|
+
| Case 5 | Một biến thể class | đi vào `classNames.ts` dưới dạng `getAiChatBubbleClassName(role)` |
|
|
56
|
+
|
|
57
|
+
## FE-FUNCTION-6 — Hình dạng hook
|
|
58
|
+
|
|
59
|
+
| Case | Dùng khi | Viết |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| Case 1 | Đọc | `export const useQueryCourseSwr = ({ displayId }: UseQueryCourseSwrParams = {}) => { const viewer = useViewerKey(); return useSWR<CourseDetail \| null>(displayId === undefined ? null : [QUERY_COURSE_SWR_KEY, displayId, viewer ?? "guest"], async () => { const result = await queryCourse({ request: { displayId } }); return result.data?.course?.data ?? null }) }` |
|
|
62
|
+
| Case 2 | Ghi | `export const useMutateAddToCartSwr = (courseId?: string) => useSWRMutation(courseId === undefined ? null : [MUTATE_ADD_TO_CART_SWR_KEY, courseId], async (_key: readonly [string, string], { arg }: AddToCartTrigger) => mutationAddToCart({ courseId: arg.courseId }))` |
|
|
63
|
+
| Case 3 | Trả về | kết quả SWR được trả nguyên; component đọc `query.data`, `query.isLoading`, `checkout.trigger`, `checkout.isMutating` |
|
|
64
|
+
| Case 4 | Khóa | `null` khi thiếu đầu vào; danh tính người xem là một phần của khóa đọc |
|
|
65
|
+
|
|
66
|
+
## FE-FUNCTION-7 — Tệp route
|
|
67
|
+
|
|
68
|
+
| Case | Dùng khi | Viết |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Case 1 | Mọi `page.tsx` | `const AuthenticationRoute = () => <AuthenticationPage {...{}} />` rồi `export default AuthenticationRoute` — route không đưa ra quyết định vẽ nào |
|