@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,87 @@
|
|
|
1
|
+
# Imports
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a frontend file, what may it import, through which path,
|
|
4
|
+
and in which order?
|
|
5
|
+
|
|
6
|
+
Sources: `tsconfig.json` (`paths`), `eslint.config.mjs`, `src/hooks/index.ts`,
|
|
7
|
+
`src/components/**`, `src/hooks/swr/**`.
|
|
8
|
+
|
|
9
|
+
## FE-IMPORTS-1 — Paths
|
|
10
|
+
|
|
11
|
+
| Case | When | Write |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Case 1 | Anything under `src/` from another folder | `@/…` — the only alias (`"@/*": ["./src/*"]`); 459 files use it |
|
|
14
|
+
| Case 2 | A Grammar object | `import { SurfaceCard, Text, Heading, Badge, Button } from "@starci/grammar/common"` (553 imports, all through `/common`) |
|
|
15
|
+
| Case 3 | HeroUI | `import { cn } from "@heroui/react"` in `classNames.ts` (136 of 151); vendor components in leaves (48) and blocks (74); page `component.tsx` never (0/49) |
|
|
16
|
+
| Case 4 | Files inside the same unit | `./component`, `./classNames`, `./index` |
|
|
17
|
+
| Case 5 | Type only | `import type { AuthMode } from "@/hooks/auth/useAuthPanel"`, `import { type CourseDetail } from "…"` (274 files) |
|
|
18
|
+
|
|
19
|
+
## FE-IMPORTS-2 — Order
|
|
20
|
+
|
|
21
|
+
Not lint-enforced (no `import/order` rule is configured). The dominant order, read from the first
|
|
22
|
+
import of 417 component files, is framework, then Grammar, then own tiers, then own unit.
|
|
23
|
+
|
|
24
|
+
| Case | When | Write |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Framework first | `react` (108 files), `next-intl` (45), `next` / `next/navigation` (16), `swr` |
|
|
27
|
+
| Case 2 | Then Grammar | `@starci/grammar/common` (95 files start here when no framework import exists) |
|
|
28
|
+
| Case 3 | Then own tiers | `@/components/branches/…`, `@/components/leaves/…`, `@/hooks`, `@/modules/…` (88 files start with `@/components`) |
|
|
29
|
+
| Case 4 | Own unit last | `import { aiChatClassNames, getAiChatBubbleClassName } from "./classNames"`, `import { AuthenticationPageBase } from "./component"` |
|
|
30
|
+
|
|
31
|
+
`blocks/ai/StarCiAiChat/component.tsx` interleaves `@starci/grammar/common` and `@/components/…`
|
|
32
|
+
imports alphabetically by binding instead; the order above is dominant, not universal.
|
|
33
|
+
|
|
34
|
+
## FE-IMPORTS-3 — Data enters a block through the hooks barrel
|
|
35
|
+
|
|
36
|
+
| Case | When | Write |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Case 1 | A block reads or writes | `import { useQueryCourseSwr } from "@/hooks"` (82 block files import `@/hooks`); the barrel docblock: "A block imports `@/hooks`, never `@/hooks/swr/useQuerySomethingSwr`" |
|
|
39
|
+
| Case 2 | A hook reaches the transport | `import { queryCourse } from "../../modules/api/graphql/queries/query-course"` (57 hook files) or `from "@/modules/api/…"` (56 hook files) — evenly split, see open question |
|
|
40
|
+
| Case 3 | A type from the data layer | `import type { CourseAdvisorRecommendation } from "@/modules/ai/course-advisor-response"` — types are not re-exported by the barrel |
|
|
41
|
+
| Case 4 | Auth identity | `import { useViewerKey } from "@/hooks/auth/useViewerKey"` inside hooks; components mock `@/hooks/auth/useSessionToken` in specs |
|
|
42
|
+
|
|
43
|
+
## FE-IMPORTS-4 — Tier direction
|
|
44
|
+
|
|
45
|
+
| Case | When | Write |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Case 1 | Page → block | `import { AuthenticationPanel } from "@/components/blocks/auth/AuthenticationPanel"` (48 of 50 pages import blocks; 11 import leaves; 8 import hooks; 1 imports modules) |
|
|
48
|
+
| Case 2 | Block → leaf, branch, Grammar, hooks, modules | `import { CodeBlock } from "@/components/leaves/CodeBlock"`, `import { Article } from "@/components/branches/Article"` (82 blocks import hooks, 67 import modules) |
|
|
49
|
+
| Case 3 | Leaf → Grammar and vendor only | `import type { ButtonVariant } from "@starci/grammar/common"`, `import { buttonVariants } from "@heroui/styles"` (1 leaf imports hooks, 1 imports a block: outliers) |
|
|
50
|
+
| Case 4 | Upward import | block → page observed once; leaf → block observed once; neither is a pattern |
|
|
51
|
+
| Case 5 | Pure half → data layer | never at runtime; 18 `component.tsx` files use `import type` from `@/modules/api/graphql/queries/…` only |
|
|
52
|
+
|
|
53
|
+
## FE-IMPORTS-5 — Vendor boundaries
|
|
54
|
+
|
|
55
|
+
| Case | When | Write |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Case 1 | Glyphs | only the Icon leaf imports `@heroicons/react` (1 component file); everything else takes the Grammar `Icon` with a source prop (lint `no-vendor-icon-outside-icon-leaf`, `heroicons-is-the-glyph-vendor`) |
|
|
58
|
+
| Case 2 | Headings | `Heading` from Grammar, never a raw `<h2>` (lint `no-heading-tag-outside-heading-component`) |
|
|
59
|
+
| Case 3 | Grammar package dependencies | `peerDependencies` are exactly `@heroui/react` and `react`; `dependencies` is empty (`package-boundary.test.mjs`) |
|
|
60
|
+
|
|
61
|
+
## FE-IMPORTS-6 — Re-exports
|
|
62
|
+
|
|
63
|
+
| Case | When | Write |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Case 1 | The hooks barrel | `export { useMutateAddToCartSwr } from "./swr/useMutateAddToCartSwr"` — one line per hook, no `export *` |
|
|
66
|
+
| Case 2 | Grammar family entry | `export { COMMON_SPACING_SCALE, COMMON_SPACING_TOKENS, type CommonSpacingStep, … } from "./spacing.js"` — named, with `.js` suffix |
|
|
67
|
+
| Case 3 | Block index re-exporting its twin | `export * from "./component"` at the end of `index.tsx` — 19 of 95 block indexes and 19 of 49 page indexes do this; the majority do not |
|
|
68
|
+
|
|
69
|
+
## FE-IMPORTS-7 — Forbidden
|
|
70
|
+
|
|
71
|
+
| Case | When | Write |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| Case 1 | Deep hook path from a component | `@/hooks/swr/useQueryCourseSwr` — escapes `vi.mock("@/hooks")` |
|
|
74
|
+
| Case 2 | Default export from `src/components` | 0 exist |
|
|
75
|
+
| Case 3 | Inline lint config | `eslint-disable` appears in 0 files; `src/components/blocks/**/{index,component}.tsx` run with `noInlineConfig: true` |
|
|
76
|
+
| Case 4 | `namespace` | only `src/modules/api/graphql/clients/options.ts`, by config exception |
|
|
77
|
+
| Case 5 | Mixing families, or importing the package root | renderers and prop types come from the family entry `@starci/grammar/core` (which re-exports every Common renderer since 0.4.2) or from `@starci/grammar/common`; never from the package root, never from two families in one app |
|
|
78
|
+
| Case 6 | A second family stylesheet under one root | one `CoreGrammarRoot` at the composition root, one `@starci/grammar/core/styles.css`; the family is selected once |
|
|
79
|
+
| Case 7 | An app-local clone of a Common renderer or an anonymous layout | `TextLink`, `NavLink`, `SeeMoreLink`, a local `Sidebar` or shell geometry: use the Common renderer and pass props; a product adapter such as `LearnShellLayout` maps routes and state into Common, it owns no geometry |
|
|
80
|
+
| Case 8 | A product name in a Grammar public export, or business logic inside Common/Core | `Learn`, `Console`, `Dashboard` stay in product code; routes, permissions, persistence and effects never enter the package |
|
|
81
|
+
|
|
82
|
+
## Open questions
|
|
83
|
+
|
|
84
|
+
- Hooks reach `modules/api` through `../../modules/api/…` in 57 files and `@/modules/api/…` in 56.
|
|
85
|
+
No variant dominates; this file does not choose.
|
|
86
|
+
- `export * from "./component"` at the end of a connected `index.tsx` is present in 19/95 blocks
|
|
87
|
+
and absent in the rest. Not legislated.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Import
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một tệp frontend, nó được import gì, qua đường dẫn nào, và theo
|
|
4
|
+
thứ tự nào?
|
|
5
|
+
|
|
6
|
+
Nguồn: `tsconfig.json` (`paths`), `eslint.config.mjs`, `src/hooks/index.ts`,
|
|
7
|
+
`src/components/**`, `src/hooks/swr/**`.
|
|
8
|
+
|
|
9
|
+
## FE-IMPORTS-1 — Đường dẫn
|
|
10
|
+
|
|
11
|
+
| Case | Dùng khi | Viết |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Case 1 | Bất cứ thứ gì dưới `src/` từ thư mục khác | `@/…` — alias duy nhất (`"@/*": ["./src/*"]`); 459 tệp dùng nó |
|
|
14
|
+
| Case 2 | Một đối tượng Grammar | `import { SurfaceCard, Text, Heading, Badge, Button } from "@starci/grammar/common"` (553 import, tất cả qua `/common`) |
|
|
15
|
+
| Case 3 | HeroUI | `import { cn } from "@heroui/react"` trong `classNames.ts` (136 trên 151); component nhà cung cấp trong leaf (48) và block (74); `component.tsx` của page không bao giờ (0/49) |
|
|
16
|
+
| Case 4 | Tệp trong cùng đơn vị | `./component`, `./classNames`, `./index` |
|
|
17
|
+
| Case 5 | Chỉ kiểu | `import type { AuthMode } from "@/hooks/auth/useAuthPanel"`, `import { type CourseDetail } from "…"` (274 tệp) |
|
|
18
|
+
|
|
19
|
+
## FE-IMPORTS-2 — Thứ tự
|
|
20
|
+
|
|
21
|
+
Không bị lint ép (không cấu hình luật `import/order`). Thứ tự chiếm ưu thế, đọc từ import đầu tiên
|
|
22
|
+
của 417 tệp component, là framework, rồi Grammar, rồi tầng của mình, rồi đơn vị của mình.
|
|
23
|
+
|
|
24
|
+
| Case | Dùng khi | Viết |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Framework trước | `react` (108 tệp), `next-intl` (45), `next` / `next/navigation` (16), `swr` |
|
|
27
|
+
| Case 2 | Rồi Grammar | `@starci/grammar/common` (95 tệp bắt đầu ở đây khi không có import framework) |
|
|
28
|
+
| Case 3 | Rồi tầng của mình | `@/components/branches/…`, `@/components/leaves/…`, `@/hooks`, `@/modules/…` (88 tệp bắt đầu bằng `@/components`) |
|
|
29
|
+
| Case 4 | Đơn vị của mình sau cùng | `import { aiChatClassNames, getAiChatBubbleClassName } from "./classNames"`, `import { AuthenticationPageBase } from "./component"` |
|
|
30
|
+
|
|
31
|
+
`blocks/ai/StarCiAiChat/component.tsx` lại xen kẽ import `@starci/grammar/common` và
|
|
32
|
+
`@/components/…` theo thứ tự chữ cái của tên binding; thứ tự trên là ưu thế, không phải toàn thể.
|
|
33
|
+
|
|
34
|
+
## FE-IMPORTS-3 — Dữ liệu vào block qua barrel hooks
|
|
35
|
+
|
|
36
|
+
| Case | Dùng khi | Viết |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Case 1 | Block đọc hoặc ghi | `import { useQueryCourseSwr } from "@/hooks"` (82 tệp block import `@/hooks`); docblock của barrel nói rõ block import `@/hooks`, không bao giờ `@/hooks/swr/useQuerySomethingSwr` |
|
|
39
|
+
| Case 2 | Hook chạm tới tầng vận chuyển | `import { queryCourse } from "../../modules/api/graphql/queries/query-course"` (57 tệp hook) hoặc `from "@/modules/api/…"` (56 tệp hook) — chia đều, xem câu hỏi để ngỏ |
|
|
40
|
+
| Case 3 | Một kiểu từ tầng dữ liệu | `import type { CourseAdvisorRecommendation } from "@/modules/ai/course-advisor-response"` — barrel không re-export kiểu |
|
|
41
|
+
| Case 4 | Danh tính xác thực | `import { useViewerKey } from "@/hooks/auth/useViewerKey"` bên trong hook; component giả lập `@/hooks/auth/useSessionToken` trong spec |
|
|
42
|
+
|
|
43
|
+
## FE-IMPORTS-4 — Chiều giữa các tầng
|
|
44
|
+
|
|
45
|
+
| Case | Dùng khi | Viết |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| Case 1 | Page → block | `import { AuthenticationPanel } from "@/components/blocks/auth/AuthenticationPanel"` (48 trên 50 page import block; 11 import leaf; 8 import hooks; 1 import modules) |
|
|
48
|
+
| Case 2 | Block → leaf, branch, Grammar, hooks, modules | `import { CodeBlock } from "@/components/leaves/CodeBlock"`, `import { Article } from "@/components/branches/Article"` (82 block import hooks, 67 import modules) |
|
|
49
|
+
| Case 3 | Leaf → chỉ Grammar và nhà cung cấp | `import type { ButtonVariant } from "@starci/grammar/common"`, `import { buttonVariants } from "@heroui/styles"` (1 leaf import hooks, 1 import block: ngoại lệ) |
|
|
50
|
+
| Case 4 | Import ngược lên | block → page thấy một lần; leaf → block thấy một lần; không cái nào là mẫu |
|
|
51
|
+
| Case 5 | Nửa thuần → tầng dữ liệu | không bao giờ lúc chạy; 18 tệp `component.tsx` chỉ dùng `import type` từ `@/modules/api/graphql/queries/…` |
|
|
52
|
+
|
|
53
|
+
## FE-IMPORTS-5 — Ranh giới nhà cung cấp
|
|
54
|
+
|
|
55
|
+
| Case | Dùng khi | Viết |
|
|
56
|
+
| --- | --- | --- |
|
|
57
|
+
| Case 1 | Ký tự biểu tượng | chỉ leaf Icon import `@heroicons/react` (1 tệp component); mọi nơi khác nhận `Icon` của Grammar với một prop nguồn (lint `no-vendor-icon-outside-icon-leaf`, `heroicons-is-the-glyph-vendor`) |
|
|
58
|
+
| Case 2 | Tiêu đề | `Heading` từ Grammar, không bao giờ `<h2>` trần (lint `no-heading-tag-outside-heading-component`) |
|
|
59
|
+
| Case 3 | Phụ thuộc của gói Grammar | `peerDependencies` đúng là `@heroui/react` và `react`; `dependencies` rỗng (`package-boundary.test.mjs`) |
|
|
60
|
+
|
|
61
|
+
## FE-IMPORTS-6 — Re-export
|
|
62
|
+
|
|
63
|
+
| Case | Dùng khi | Viết |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Case 1 | Barrel hooks | `export { useMutateAddToCartSwr } from "./swr/useMutateAddToCartSwr"` — một dòng cho mỗi hook, không `export *` |
|
|
66
|
+
| Case 2 | Cửa vào họ Grammar | `export { COMMON_SPACING_SCALE, COMMON_SPACING_TOKENS, type CommonSpacingStep, … } from "./spacing.js"` — có tên, kèm hậu tố `.js` |
|
|
67
|
+
| Case 3 | Index của block re-export bản sao | `export * from "./component"` ở cuối `index.tsx` — 19 trên 95 index block và 19 trên 49 index page làm vậy; đa số không |
|
|
68
|
+
|
|
69
|
+
## FE-IMPORTS-7 — Bị cấm
|
|
70
|
+
|
|
71
|
+
| Case | Dùng khi | Viết |
|
|
72
|
+
| --- | --- | --- |
|
|
73
|
+
| Case 1 | Đường dẫn sâu tới hook từ component | `@/hooks/swr/useQueryCourseSwr` — thoát khỏi `vi.mock("@/hooks")` |
|
|
74
|
+
| Case 2 | Default export trong `src/components` | 0 tồn tại |
|
|
75
|
+
| Case 3 | Cấu hình lint nội tuyến | `eslint-disable` xuất hiện trong 0 tệp; `src/components/blocks/**/{index,component}.tsx` chạy với `noInlineConfig: true` |
|
|
76
|
+
| Case 4 | `namespace` | chỉ `src/modules/api/graphql/clients/options.ts`, theo ngoại lệ ghi trong cấu hình |
|
|
77
|
+
| Case 5 | Trộn family, hay import từ gốc gói | renderer và kiểu props lấy từ entry family `@starci/grammar/core` (từ 0.4.2 re-export toàn bộ renderer của Common) hoặc từ `@starci/grammar/common`; không bao giờ từ gốc gói, không bao giờ hai family trong một app |
|
|
78
|
+
| Case 6 | Stylesheet của family thứ hai dưới cùng một root | một `CoreGrammarRoot` ở root của composition, một `@starci/grammar/core/styles.css`; family được chọn đúng một lần |
|
|
79
|
+
| Case 7 | Bản sao cục bộ của một renderer Common hay một layout vô danh | `TextLink`, `NavLink`, `SeeMoreLink`, `Sidebar` hay hình học shell tự dựng: dùng renderer của Common và truyền prop; adapter sản phẩm như `LearnShellLayout` chỉ ánh xạ route và state vào Common, không sở hữu hình học |
|
|
80
|
+
| Case 8 | Tên sản phẩm trong export công khai của Grammar, hay logic nghiệp vụ bên trong Common/Core | `Learn`, `Console`, `Dashboard` ở lại code sản phẩm; route, quyền, persistence và effect không bao giờ vào gói |
|
|
81
|
+
|
|
82
|
+
## Câu hỏi để ngỏ
|
|
83
|
+
|
|
84
|
+
- Hook chạm tới `modules/api` qua `../../modules/api/…` ở 57 tệp và `@/modules/api/…` ở 56 tệp.
|
|
85
|
+
Không biến thể nào chiếm ưu thế; tệp này không chọn.
|
|
86
|
+
- `export * from "./component"` ở cuối `index.tsx` nối dữ liệu có ở 19/95 block và vắng ở phần còn
|
|
87
|
+
lại. Không áp đặt.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Naming
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a folder, export, type, constant or function in the
|
|
4
|
+
frontend, what is it called?
|
|
5
|
+
|
|
6
|
+
Sources: `src/components/pages/*`, `src/components/blocks/*/*`, `src/components/leaves/*`,
|
|
7
|
+
`src/hooks/swr/*`, `src/modules/api/graphql/**`.
|
|
8
|
+
|
|
9
|
+
## FE-NAMING-1 — Unit folder and its two exports
|
|
10
|
+
|
|
11
|
+
The folder name is the connected export; the pure twin appends `Base`.
|
|
12
|
+
|
|
13
|
+
| Case | When | Write |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Page connected half, `pages/CartPage/index.tsx` | `export const CartPage = (props: CartPageProps) => …` (49/49 pages) |
|
|
16
|
+
| Case 2 | Page pure half, `pages/AuthenticationPage/component.tsx` | `export const AuthenticationPageBase = (props: AuthenticationPageProps) => …` (47/49; the two `CoursePlayground*Page` units export `…PageShell`) |
|
|
17
|
+
| Case 3 | Block pure half, `blocks/ai/StarCiAiChat/component.tsx` | `export const StarCiAiChatBase = …` (98 of 118 `component.tsx` exports end in `Base`) |
|
|
18
|
+
| Case 4 | Leaf, `leaves/ButtonStateSample/index.tsx` | `export const ButtonStateSample = …` — no `Base`, no twin |
|
|
19
|
+
| Case 5 | Route wrapper, `app/[lang]/authentication/page.tsx` | `const AuthenticationRoute = () => …` |
|
|
20
|
+
|
|
21
|
+
## FE-NAMING-2 — Props and its parts
|
|
22
|
+
|
|
23
|
+
| Case | When | Write |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Case 1 | The props type | `export type StarCiAiChatProps = { … }` (403 `export type …Props` against 6 `export interface …Props`) |
|
|
26
|
+
| Case 2 | Resolved facts | `export type StarCiAiChatData = { … }`, `export type ButtonStateSampleData = { … }` |
|
|
27
|
+
| Case 3 | Resolved copy | `export type StarCiAiChatLabels = { … }`, `CourseAdvisorRecommendationCardLabels` |
|
|
28
|
+
| Case 4 | Handlers | `export type StarCiAiChatActions = { … }`, `AuthenticationPageActions` |
|
|
29
|
+
| Case 5 | State inventory | `export type StarCiAiChatState = "sessionsPending" \| …`; `export type StarCiAiMode = "general" \| "history"` |
|
|
30
|
+
| Case 6 | The same name in both halves | `AuthenticationPageProps` is declared in `component.tsx` and again in `index.tsx` with a different shape; each file owns its own `XProps` |
|
|
31
|
+
|
|
32
|
+
## FE-NAMING-3 — Class-name exports
|
|
33
|
+
|
|
34
|
+
| Case | When | Write |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Case 1 | One string for one role | `export const authenticationPageClassName = cn(formPageClassName)` (931 `…ClassName` exports) |
|
|
37
|
+
| Case 2 | A map of roles for one unit | `export const aiChatClassNames = { root: cn(…), intro: cn(…), … } as const` (9 `…ClassNames` exports; 7 of 151 `classNames.ts` files use the object form) |
|
|
38
|
+
| Case 3 | A variant resolved at call time | `export const getAiChatBubbleClassName = (role: "user" \| "assistant") => …`; `export const getButtonStateSampleClassName = (variant: …) => buttonVariants({ variant, size: "sm" })` |
|
|
39
|
+
| Case 4 | Grammar package | `export const railClassName`, `railFrameClassName`, `railBodyClassName`, `railFooterClassName` (`core/branch/Rail/classNames.ts`) |
|
|
40
|
+
|
|
41
|
+
## FE-NAMING-4 — Hooks and keys
|
|
42
|
+
|
|
43
|
+
| Case | When | Write |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Case 1 | Read hook | `useQueryCourseSwr`, `useQueryMyCartSwr` (80 files `useQuery…Swr.ts`) |
|
|
46
|
+
| Case 2 | Write hook | `useMutateAddToCartSwr`, `useMutateClearCartSwr` (32 files `useMutate…Swr.ts`) |
|
|
47
|
+
| Case 3 | Key constant | `export const QUERY_COURSE_SWR_KEY = "QUERY_COURSE_SWR"`, `export const MUTATE_ADD_TO_CART_SWR_KEY = "MUTATE_ADD_TO_CART_SWR"` |
|
|
48
|
+
| Case 4 | Hook params type | `export interface UseQueryCourseSwrParams { displayId?: string }`; trigger arg `export type AddToCartArg`, `export type AddToCartTrigger` |
|
|
49
|
+
| Case 5 | Non-data hooks | `useSessionToken`, `useViewerKey`, `useAuthPanel` under `hooks/auth/` |
|
|
50
|
+
| Case 6 | GraphQL document module | `modules/api/graphql/queries/query-course.ts`, `mutations/mutation-add-to-cart.ts`; the document const is `query1`, the function `queryCourse` / `mutationAddToCart` |
|
|
51
|
+
|
|
52
|
+
## FE-NAMING-5 — Constants and module-level values
|
|
53
|
+
|
|
54
|
+
| Case | When | Write |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| Case 1 | Frozen module value | `const HISTORY_STATES = new Set<StarCiAiChatState>([…])`, `const PENDING_TURN_IDS = ["pending-1", "pending-2"] as const`, `const DEFAULT_MESSAGES: ToastMessages = { … }` (120 exported + 163 local `UPPER_SNAKE` consts) |
|
|
57
|
+
| Case 2 | Exported inventory | `export const STARCI_AI_CHAT_STATES: ReadonlyArray<StarCiAiChatState> = […]` |
|
|
58
|
+
| Case 3 | Grammar lookup tables | `const VARIANTS = { primary: "primary", … } as const`, `const SIZES = { sm: "sm", md: "md", lg: "lg" } as const`, `const SKELETON_CLASS_NAME = …` (`core/primitive/Button/index.tsx`) |
|
|
59
|
+
| Case 4 | Operation-name enums (the only enums in `src/`) | `export enum MutationCvBlocks { Create = "create", Update = "update", Render = "render", Rewrite = "rewrite" }` (92, all in `modules/api/graphql`) |
|
|
60
|
+
|
|
61
|
+
## FE-NAMING-6 — Handlers, booleans, helpers
|
|
62
|
+
|
|
63
|
+
| Case | When | Write |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Case 1 | Action handler | bare verb inside the `on` object: `on?: { signedIn?: () => void }`, `on?.send`, `on?.selectMode?.("general")` — never `onX` on the pure half (lint `handler-on-prefix`) |
|
|
66
|
+
| Case 2 | Boolean prop | `is`/`has` prefix: `isLoading`, `isEnrolled`, `isPartial`, `isArchived`, `isOnline` (286 of 366 boolean props) |
|
|
67
|
+
| Case 3 | Local pure helper | verb or noun phrase in camelCase: `stateNeedsRetry`, `turnMarkdown`, `routeState` |
|
|
68
|
+
| Case 4 | Local sub-render | PascalCase with its own props type: `const Turn = (props: TurnProps) => …` |
|
|
69
|
+
|
|
70
|
+
## FE-NAMING-7 — File names outside component folders
|
|
71
|
+
|
|
72
|
+
| Case | When | Write |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Case 1 | Hook file | camelCase equal to the export: `useQueryCourseSwr.ts`, `useSessionToken.ts` |
|
|
75
|
+
| Case 2 | Module file | kebab-case: `query-course.ts`, `course-advisor-response.ts`, `content-ai-selection-context.ts`, `create-apollo-client.ts` |
|
|
76
|
+
| Case 3 | Spec file | same basename plus `.spec`: `useQueryCourseSwr.spec.ts`, `component.spec.tsx`, `index.spec.tsx` |
|
|
77
|
+
| Case 4 | Spec `describe` | the export name: `describe("StarCiAiChatBase"` in `component.spec.tsx` (85/97), `describe("CartBlock"` in `index.spec.tsx` (0 `…Base` there) |
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Đặt tên
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một thư mục, export, kiểu, hằng hay hàm trong frontend, nó được
|
|
4
|
+
gọi là gì?
|
|
5
|
+
|
|
6
|
+
Nguồn: `src/components/pages/*`, `src/components/blocks/*/*`, `src/components/leaves/*`,
|
|
7
|
+
`src/hooks/swr/*`, `src/modules/api/graphql/**`.
|
|
8
|
+
|
|
9
|
+
## FE-NAMING-1 — Thư mục đơn vị và hai export của nó
|
|
10
|
+
|
|
11
|
+
Tên thư mục là export nối dữ liệu; bản sao thuần thêm hậu tố `Base`.
|
|
12
|
+
|
|
13
|
+
| Case | Dùng khi | Viết |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Nửa nối của page, `pages/CartPage/index.tsx` | `export const CartPage = (props: CartPageProps) => …` (49/49 page) |
|
|
16
|
+
| Case 2 | Nửa thuần của page, `pages/AuthenticationPage/component.tsx` | `export const AuthenticationPageBase = (props: AuthenticationPageProps) => …` (47/49; hai đơn vị `CoursePlayground*Page` export `…PageShell`) |
|
|
17
|
+
| Case 3 | Nửa thuần của block, `blocks/ai/StarCiAiChat/component.tsx` | `export const StarCiAiChatBase = …` (98 trên 118 export trong `component.tsx` kết thúc bằng `Base`) |
|
|
18
|
+
| Case 4 | Leaf, `leaves/ButtonStateSample/index.tsx` | `export const ButtonStateSample = …` — không `Base`, không bản sao |
|
|
19
|
+
| Case 5 | Vỏ route, `app/[lang]/authentication/page.tsx` | `const AuthenticationRoute = () => …` |
|
|
20
|
+
|
|
21
|
+
## FE-NAMING-2 — Props và các phần của nó
|
|
22
|
+
|
|
23
|
+
| Case | Dùng khi | Viết |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Case 1 | Kiểu props | `export type StarCiAiChatProps = { … }` (403 `export type …Props` so với 6 `export interface …Props`) |
|
|
26
|
+
| Case 2 | Dữ kiện đã giải | `export type StarCiAiChatData = { … }`, `export type ButtonStateSampleData = { … }` |
|
|
27
|
+
| Case 3 | Câu chữ đã giải | `export type StarCiAiChatLabels = { … }`, `CourseAdvisorRecommendationCardLabels` |
|
|
28
|
+
| Case 4 | Bộ xử lý | `export type StarCiAiChatActions = { … }`, `AuthenticationPageActions` |
|
|
29
|
+
| Case 5 | Kho trạng thái | `export type StarCiAiChatState = "sessionsPending" \| …`; `export type StarCiAiMode = "general" \| "history"` |
|
|
30
|
+
| Case 6 | Cùng tên ở cả hai nửa | `AuthenticationPageProps` được khai báo trong `component.tsx` và khai báo lại trong `index.tsx` với hình dạng khác; mỗi tệp sở hữu `XProps` của riêng nó |
|
|
31
|
+
|
|
32
|
+
## FE-NAMING-3 — Export class-name
|
|
33
|
+
|
|
34
|
+
| Case | Dùng khi | Viết |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Case 1 | Một chuỗi cho một vai trò | `export const authenticationPageClassName = cn(formPageClassName)` (931 export `…ClassName`) |
|
|
37
|
+
| Case 2 | Một bản đồ vai trò cho một đơn vị | `export const aiChatClassNames = { root: cn(…), intro: cn(…), … } as const` (9 export `…ClassNames`; 7 trên 151 tệp `classNames.ts` dùng dạng object) |
|
|
38
|
+
| Case 3 | Biến thể giải lúc gọi | `export const getAiChatBubbleClassName = (role: "user" \| "assistant") => …`; `export const getButtonStateSampleClassName = (variant: …) => buttonVariants({ variant, size: "sm" })` |
|
|
39
|
+
| Case 4 | Gói Grammar | `export const railClassName`, `railFrameClassName`, `railBodyClassName`, `railFooterClassName` (`core/branch/Rail/classNames.ts`) |
|
|
40
|
+
|
|
41
|
+
## FE-NAMING-4 — Hook và khóa
|
|
42
|
+
|
|
43
|
+
| Case | Dùng khi | Viết |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Case 1 | Hook đọc | `useQueryCourseSwr`, `useQueryMyCartSwr` (80 tệp `useQuery…Swr.ts`) |
|
|
46
|
+
| Case 2 | Hook ghi | `useMutateAddToCartSwr`, `useMutateClearCartSwr` (32 tệp `useMutate…Swr.ts`) |
|
|
47
|
+
| Case 3 | Hằng khóa | `export const QUERY_COURSE_SWR_KEY = "QUERY_COURSE_SWR"`, `export const MUTATE_ADD_TO_CART_SWR_KEY = "MUTATE_ADD_TO_CART_SWR"` |
|
|
48
|
+
| Case 4 | Kiểu tham số hook | `export interface UseQueryCourseSwrParams { displayId?: string }`; đối số trigger `export type AddToCartArg`, `export type AddToCartTrigger` |
|
|
49
|
+
| Case 5 | Hook không phải dữ liệu | `useSessionToken`, `useViewerKey`, `useAuthPanel` dưới `hooks/auth/` |
|
|
50
|
+
| Case 6 | Module tài liệu GraphQL | `modules/api/graphql/queries/query-course.ts`, `mutations/mutation-add-to-cart.ts`; hằng tài liệu là `query1`, hàm là `queryCourse` / `mutationAddToCart` |
|
|
51
|
+
|
|
52
|
+
## FE-NAMING-5 — Hằng và giá trị cấp module
|
|
53
|
+
|
|
54
|
+
| Case | Dùng khi | Viết |
|
|
55
|
+
| --- | --- | --- |
|
|
56
|
+
| Case 1 | Giá trị module đóng băng | `const HISTORY_STATES = new Set<StarCiAiChatState>([…])`, `const PENDING_TURN_IDS = ["pending-1", "pending-2"] as const`, `const DEFAULT_MESSAGES: ToastMessages = { … }` (120 hằng `UPPER_SNAKE` export + 163 cục bộ) |
|
|
57
|
+
| Case 2 | Kho export | `export const STARCI_AI_CHAT_STATES: ReadonlyArray<StarCiAiChatState> = […]` |
|
|
58
|
+
| Case 3 | Bảng tra trong Grammar | `const VARIANTS = { primary: "primary", … } as const`, `const SIZES = { sm: "sm", md: "md", lg: "lg" } as const`, `const SKELETON_CLASS_NAME = …` (`core/primitive/Button/index.tsx`) |
|
|
59
|
+
| Case 4 | Enum tên thao tác (enum duy nhất trong `src/`) | `export enum MutationCvBlocks { Create = "create", Update = "update", Render = "render", Rewrite = "rewrite" }` (92, toàn bộ trong `modules/api/graphql`) |
|
|
60
|
+
|
|
61
|
+
## FE-NAMING-6 — Bộ xử lý, boolean, helper
|
|
62
|
+
|
|
63
|
+
| Case | Dùng khi | Viết |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| Case 1 | Bộ xử lý hành động | động từ trần trong object `on`: `on?: { signedIn?: () => void }`, `on?.send`, `on?.selectMode?.("general")` — không bao giờ `onX` ở nửa thuần (lint `handler-on-prefix`) |
|
|
66
|
+
| Case 2 | Prop boolean | tiền tố `is`/`has`: `isLoading`, `isEnrolled`, `isPartial`, `isArchived`, `isOnline` (286 trên 366 prop boolean) |
|
|
67
|
+
| Case 3 | Helper thuần cục bộ | cụm động từ hoặc danh từ camelCase: `stateNeedsRetry`, `turnMarkdown`, `routeState` |
|
|
68
|
+
| Case 4 | Bộ vẽ con cục bộ | PascalCase với kiểu props riêng: `const Turn = (props: TurnProps) => …` |
|
|
69
|
+
|
|
70
|
+
## FE-NAMING-7 — Tên tệp ngoài thư mục component
|
|
71
|
+
|
|
72
|
+
| Case | Dùng khi | Viết |
|
|
73
|
+
| --- | --- | --- |
|
|
74
|
+
| Case 1 | Tệp hook | camelCase trùng export: `useQueryCourseSwr.ts`, `useSessionToken.ts` |
|
|
75
|
+
| Case 2 | Tệp module | kebab-case: `query-course.ts`, `course-advisor-response.ts`, `content-ai-selection-context.ts`, `create-apollo-client.ts` |
|
|
76
|
+
| Case 3 | Tệp spec | cùng tên gốc cộng `.spec`: `useQueryCourseSwr.spec.ts`, `component.spec.tsx`, `index.spec.tsx` |
|
|
77
|
+
| Case 4 | `describe` trong spec | tên export: `describe("StarCiAiChatBase"` trong `component.spec.tsx` (85/97), `describe("CartBlock"` trong `index.spec.tsx` (0 `…Base` ở đó) |
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Test
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a frontend unit, where does its spec live, what does it
|
|
4
|
+
assert, and what does it leave alone?
|
|
5
|
+
|
|
6
|
+
Sources: `src/components/pages/CartPage/index.spec.tsx`,
|
|
7
|
+
`pages/CodingDomainPage/component.spec.tsx`, `blocks/commerce/CartBlock/index.spec.tsx`,
|
|
8
|
+
`hooks/index.ts`, `packages/grammar/src/**/*.spec.tsx`, `packages/grammar/src/**/*.test.mjs`,
|
|
9
|
+
`package.json` scripts (`vitest run`).
|
|
10
|
+
|
|
11
|
+
## FE-TEST-1 — Placement
|
|
12
|
+
|
|
13
|
+
| Case | When | Write |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Pure half | `component.spec.tsx` beside `component.tsx` (55 blocks, 42 pages) |
|
|
16
|
+
| Case 2 | Connected half | `index.spec.tsx` beside `index.tsx` (61 blocks, 24 pages, 19 leaves) |
|
|
17
|
+
| Case 3 | Hook | `useQueryCourseSwr.spec.ts` beside the hook |
|
|
18
|
+
| Case 4 | Class strings | `classNames.spec.ts` beside `classNames.ts` (2 in blocks); Grammar CSS proof in `styles.spec.ts` (6) |
|
|
19
|
+
| Case 5 | Anywhere else | 0 of 497 specs live in a `__tests__/` folder |
|
|
20
|
+
| Case 6 | Runner | `vitest`, `@testing-library/react`; `describe/it/expect/vi` imported from `"vitest"` |
|
|
21
|
+
|
|
22
|
+
## FE-TEST-2 — Connected spec: mock the doors, assert the props handed down
|
|
23
|
+
|
|
24
|
+
| Case | When | Write |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | Hoisted mock state | `const mocks = vi.hoisted(() => ({ input: undefined as TestInput \| undefined, token: "token" as string \| undefined, cart: { data: undefined as unknown, error: undefined as unknown, isLoading: false, mutate: vi.fn() }, … }))` |
|
|
27
|
+
| Case 2 | The hooks barrel | `vi.mock("@/hooks", () => ({ useQueryMyCartSwr: () => mocks.cart, useQueryCoursesCheckoutPreviewSwr: () => mocks.preview, … }))` (25 of 61 block index specs) |
|
|
28
|
+
| Case 3 | Translation and routing | `vi.mock("next-intl", () => ({ useLocale: () => "en", useTranslations: () => (key: string) => key }))`, `vi.mock("@/i18n/navigation", () => ({ useRouter: () => ({ push: mocks.push }) }))` |
|
|
29
|
+
| Case 4 | The pure twin | `vi.mock("./component", () => ({ CartBlockBase: (input: TestInput) => { mocks.input = input; return <output data-testid="cart" /> } }))` |
|
|
30
|
+
| Case 5 | Import after mocks | `import { CartBlock } from "./index"` placed after the `vi.mock` calls |
|
|
31
|
+
| Case 6 | Assertion | `expect(mocks.input?.blockState).toBe("empty")`, `act(() => { mocks.input?.on.pay() })`, `expect(mocks.checkout.trigger).toHaveBeenCalledWith(expect.objectContaining({ paymentType: "payos" }))` |
|
|
32
|
+
| Case 7 | Reset | `beforeEach(() => { vi.clearAllMocks(); mocks.input = undefined; … })` |
|
|
33
|
+
|
|
34
|
+
## FE-TEST-3 — Pure spec: render fixtures, assert what a reader would find
|
|
35
|
+
|
|
36
|
+
| Case | When | Write |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Case 1 | Render | `render(<CodingDomainPageBase domain="arrays" navHome="Home" navPractice="Practice" title="Arrays" />)` |
|
|
39
|
+
| Case 2 | Landmarks and text | `expect(screen.getByRole("heading", { name: "Arrays" })).toBeInTheDocument()` (185 specs use `getByRole`, 173 `getByText`, 60 `getByTestId`) |
|
|
40
|
+
| Case 3 | Child owners stubbed | `vi.mock("@/components/blocks/coding/CodingDomainStanding", () => ({ CodingDomainStanding: () => <div data-testid="domain-standing" /> }))` then `expect(screen.getByTestId("domain-standing")).toBeInTheDocument()` |
|
|
41
|
+
| Case 4 | Page composes without owning | `it("composes the connected cart block without owning its state", () => { render(<CartPage />); expect(screen.getByTestId("cart-block")).toHaveTextContent("cart-block") })` |
|
|
42
|
+
| Case 5 | State inventory | pure specs iterate the exported `X_STATES` array to render every member |
|
|
43
|
+
|
|
44
|
+
## FE-TEST-4 — Names
|
|
45
|
+
|
|
46
|
+
| Case | When | Write |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| Case 1 | `describe` | the export under test: `describe("CodingDomainPageBase"`, `describe("CartBlock"`, `describe("CartPage route"` (85 of 97 `component.spec` use the `…Base` name; 0 `index.spec` do) |
|
|
49
|
+
| Case 2 | `it` | a sentence about behaviour: `"keeps topic anatomy while composing connected standing and problem owners"`, `"maps cart states and dispatches browse, clear and checkout actions"` |
|
|
50
|
+
|
|
51
|
+
## FE-TEST-5 — Not asserted
|
|
52
|
+
|
|
53
|
+
| Case | When | Write |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Case 1 | Snapshots | 0 `toMatchSnapshot` / `toMatchInlineSnapshot` in `src/` |
|
|
56
|
+
| Case 2 | Network | never reached; `@/hooks` is mocked wholesale, which is why the barrel exists |
|
|
57
|
+
| Case 3 | Translation content | `useTranslations: () => (key: string) => key` — a spec asserts keys, not copy |
|
|
58
|
+
| Case 4 | Class strings in a component spec | dominant practice is not to; see open question |
|
|
59
|
+
|
|
60
|
+
## FE-TEST-6 — Grammar package
|
|
61
|
+
|
|
62
|
+
| Case | When | Write |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| Case 1 | Component behaviour | `index.spec.tsx` beside `index.tsx` (24) |
|
|
65
|
+
| Case 2 | Built output | `common/index.test.mjs`: `import test from "node:test"` … `assert.equal(COMMON_UI_RULE_IDS.length, 117)` against `../../dist/common/index.js` |
|
|
66
|
+
| Case 3 | Package surface | `package-boundary.test.mjs`: `assert.deepEqual(Object.keys(packageJson.peerDependencies).sort(), ["@heroui/react", "react"])` |
|
|
67
|
+
| Case 4 | Family-wide proof | `core/family.spec.tsx`, `core/surface-card-family.spec.tsx`, `core/scrollable-surfaces.spec.tsx` |
|
|
68
|
+
|
|
69
|
+
## Open question
|
|
70
|
+
|
|
71
|
+
64 of 272 component specs contain `toHaveClass` or `className`. The other 208 assert roles, text
|
|
72
|
+
and handed-down props only, and class proof has dedicated homes (`classNames.spec.ts`,
|
|
73
|
+
`styles.spec.ts`). The majority practice is recorded; the 64 are not declared wrong here.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Kiểm thử
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một đơn vị frontend, spec của nó nằm đâu, khẳng định gì, và
|
|
4
|
+
để yên điều gì?
|
|
5
|
+
|
|
6
|
+
Nguồn: `src/components/pages/CartPage/index.spec.tsx`,
|
|
7
|
+
`pages/CodingDomainPage/component.spec.tsx`, `blocks/commerce/CartBlock/index.spec.tsx`,
|
|
8
|
+
`hooks/index.ts`, `packages/grammar/src/**/*.spec.tsx`, `packages/grammar/src/**/*.test.mjs`,
|
|
9
|
+
script trong `package.json` (`vitest run`).
|
|
10
|
+
|
|
11
|
+
## FE-TEST-1 — Vị trí
|
|
12
|
+
|
|
13
|
+
| Case | Dùng khi | Viết |
|
|
14
|
+
| --- | --- | --- |
|
|
15
|
+
| Case 1 | Nửa thuần | `component.spec.tsx` cạnh `component.tsx` (55 block, 42 page) |
|
|
16
|
+
| Case 2 | Nửa nối | `index.spec.tsx` cạnh `index.tsx` (61 block, 24 page, 19 leaf) |
|
|
17
|
+
| Case 3 | Hook | `useQueryCourseSwr.spec.ts` cạnh hook |
|
|
18
|
+
| Case 4 | Chuỗi class | `classNames.spec.ts` cạnh `classNames.ts` (2 trong block); chứng minh CSS của Grammar trong `styles.spec.ts` (6) |
|
|
19
|
+
| Case 5 | Nơi khác | 0 trên 497 spec nằm trong thư mục `__tests__/` |
|
|
20
|
+
| Case 6 | Bộ chạy | `vitest`, `@testing-library/react`; `describe/it/expect/vi` import từ `"vitest"` |
|
|
21
|
+
|
|
22
|
+
## FE-TEST-2 — Spec nửa nối: giả lập các cánh cửa, khẳng định props được trao xuống
|
|
23
|
+
|
|
24
|
+
| Case | Dùng khi | Viết |
|
|
25
|
+
| --- | --- | --- |
|
|
26
|
+
| Case 1 | State giả lập được hoist | `const mocks = vi.hoisted(() => ({ input: undefined as TestInput \| undefined, token: "token" as string \| undefined, cart: { data: undefined as unknown, error: undefined as unknown, isLoading: false, mutate: vi.fn() }, … }))` |
|
|
27
|
+
| Case 2 | Barrel hooks | `vi.mock("@/hooks", () => ({ useQueryMyCartSwr: () => mocks.cart, useQueryCoursesCheckoutPreviewSwr: () => mocks.preview, … }))` (25 trên 61 spec index của block) |
|
|
28
|
+
| Case 3 | Dịch và điều hướng | `vi.mock("next-intl", () => ({ useLocale: () => "en", useTranslations: () => (key: string) => key }))`, `vi.mock("@/i18n/navigation", () => ({ useRouter: () => ({ push: mocks.push }) }))` |
|
|
29
|
+
| Case 4 | Bản sao thuần | `vi.mock("./component", () => ({ CartBlockBase: (input: TestInput) => { mocks.input = input; return <output data-testid="cart" /> } }))` |
|
|
30
|
+
| Case 5 | Import sau các mock | `import { CartBlock } from "./index"` đặt sau các lời gọi `vi.mock` |
|
|
31
|
+
| Case 6 | Khẳng định | `expect(mocks.input?.blockState).toBe("empty")`, `act(() => { mocks.input?.on.pay() })`, `expect(mocks.checkout.trigger).toHaveBeenCalledWith(expect.objectContaining({ paymentType: "payos" }))` |
|
|
32
|
+
| Case 7 | Đặt lại | `beforeEach(() => { vi.clearAllMocks(); mocks.input = undefined; … })` |
|
|
33
|
+
|
|
34
|
+
## FE-TEST-3 — Spec nửa thuần: vẽ fixture, khẳng định điều người đọc sẽ tìm thấy
|
|
35
|
+
|
|
36
|
+
| Case | Dùng khi | Viết |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| Case 1 | Vẽ | `render(<CodingDomainPageBase domain="arrays" navHome="Home" navPractice="Practice" title="Arrays" />)` |
|
|
39
|
+
| Case 2 | Mốc và chữ | `expect(screen.getByRole("heading", { name: "Arrays" })).toBeInTheDocument()` (185 spec dùng `getByRole`, 173 `getByText`, 60 `getByTestId`) |
|
|
40
|
+
| Case 3 | Chủ sở hữu con bị thay thế | `vi.mock("@/components/blocks/coding/CodingDomainStanding", () => ({ CodingDomainStanding: () => <div data-testid="domain-standing" /> }))` rồi `expect(screen.getByTestId("domain-standing")).toBeInTheDocument()` |
|
|
41
|
+
| Case 4 | Page ghép mà không sở hữu | `it("composes the connected cart block without owning its state", () => { render(<CartPage />); expect(screen.getByTestId("cart-block")).toHaveTextContent("cart-block") })` |
|
|
42
|
+
| Case 5 | Kho trạng thái | spec thuần lặp qua mảng `X_STATES` được export để vẽ từng thành viên |
|
|
43
|
+
|
|
44
|
+
## FE-TEST-4 — Tên
|
|
45
|
+
|
|
46
|
+
| Case | Dùng khi | Viết |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| Case 1 | `describe` | export đang được thử: `describe("CodingDomainPageBase"`, `describe("CartBlock"`, `describe("CartPage route"` (85 trên 97 `component.spec` dùng tên `…Base`; 0 `index.spec` dùng) |
|
|
49
|
+
| Case 2 | `it` | một câu về hành vi: `"keeps topic anatomy while composing connected standing and problem owners"`, `"maps cart states and dispatches browse, clear and checkout actions"` |
|
|
50
|
+
|
|
51
|
+
## FE-TEST-5 — Không khẳng định
|
|
52
|
+
|
|
53
|
+
| Case | Dùng khi | Viết |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Case 1 | Snapshot | 0 `toMatchSnapshot` / `toMatchInlineSnapshot` trong `src/` |
|
|
56
|
+
| Case 2 | Mạng | không bao giờ chạm tới; `@/hooks` bị giả lập toàn bộ, đó là lý do barrel tồn tại |
|
|
57
|
+
| Case 3 | Nội dung bản dịch | `useTranslations: () => (key: string) => key` — spec khẳng định khóa, không khẳng định câu chữ |
|
|
58
|
+
| Case 4 | Chuỗi class trong spec component | thực hành chiếm ưu thế là không; xem câu hỏi để ngỏ |
|
|
59
|
+
|
|
60
|
+
## FE-TEST-6 — Gói Grammar
|
|
61
|
+
|
|
62
|
+
| Case | Dùng khi | Viết |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| Case 1 | Hành vi component | `index.spec.tsx` cạnh `index.tsx` (24) |
|
|
65
|
+
| Case 2 | Bản build | `common/index.test.mjs`: `import test from "node:test"` … `assert.equal(COMMON_UI_RULE_IDS.length, 117)` chạy trên `../../dist/common/index.js` |
|
|
66
|
+
| Case 3 | Bề mặt gói | `package-boundary.test.mjs`: `assert.deepEqual(Object.keys(packageJson.peerDependencies).sort(), ["@heroui/react", "react"])` |
|
|
67
|
+
| Case 4 | Chứng minh toàn họ | `core/family.spec.tsx`, `core/surface-card-family.spec.tsx`, `core/scrollable-surfaces.spec.tsx` |
|
|
68
|
+
|
|
69
|
+
## Câu hỏi để ngỏ
|
|
70
|
+
|
|
71
|
+
64 trên 272 spec component chứa `toHaveClass` hoặc `className`. 208 spec còn lại chỉ khẳng định
|
|
72
|
+
vai trò, chữ và props được trao xuống, còn chứng minh class đã có nhà riêng (`classNames.spec.ts`,
|
|
73
|
+
`styles.spec.ts`). Thực hành đa số được ghi lại; 64 spec kia không bị tuyên là sai ở đây.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Typing
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a frontend value, how is its type declared?
|
|
4
|
+
|
|
5
|
+
Sources: `tsconfig.json` (`strict: true`), `eslint.config.mjs` (`@typescript-eslint/array-type`
|
|
6
|
+
generic), `src/components/**`, `src/hooks/swr/*`, `src/modules/toast/api.ts`,
|
|
7
|
+
`packages/grammar/src/common/conformance.ts`, `core/primitive/Button/index.tsx`.
|
|
8
|
+
|
|
9
|
+
## FE-TYPING-1 — `type` for shapes, `interface` in the hook layer
|
|
10
|
+
|
|
11
|
+
| Case | When | Write |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Case 1 | Props and data shapes | `export type StarCiAiTurn = { readonly id: string; readonly role: "user" \| "assistant"; … }` (919 `export type X = {` against 232 `export interface` in `src/`, 403 vs 6 for `…Props`) |
|
|
14
|
+
| Case 2 | Hook params | `export interface UseQueryCourseSwrParams { displayId?: string }` — the `interface` share sits in `hooks/` and `modules/api`, not in components |
|
|
15
|
+
| Case 3 | Intersection | `export type CourseAdvisorRecommendationCardData = CourseAdvisorRecommendation & { readonly title?: string; … }` |
|
|
16
|
+
| Case 4 | Grammar package | `export type ButtonProps = { … }`, `export type GrammarRuleConformance = { … }` — `type` throughout |
|
|
17
|
+
|
|
18
|
+
## FE-TYPING-2 — `readonly` on every field
|
|
19
|
+
|
|
20
|
+
| Case | When | Write |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Case 1 | Props field | `readonly measure?: "form" \| "formCompact"` (2336 `readonly` prop lines against 69 without) |
|
|
23
|
+
| Case 2 | Nested object | `readonly action: { readonly href: string; readonly label: string }` |
|
|
24
|
+
| Case 3 | Collection | `readonly turns: ReadonlyArray<StarCiAiTurn>`, `readonly states: Readonly<Record<StarCiAiChatState, string>>` |
|
|
25
|
+
| Case 4 | Tuple key | `_key: readonly [string, string]` |
|
|
26
|
+
|
|
27
|
+
## FE-TYPING-3 — Literal unions, not booleans, for state
|
|
28
|
+
|
|
29
|
+
| Case | When | Write |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| Case 1 | Component state | `export type StarCiAiChatState = \| "sessionsPending" \| "sessionsFailed" \| "noSession" \| … \| "contextCleared"` (21 members) |
|
|
32
|
+
| Case 2 | Mode | `export type StarCiAiMode = "general" \| "history"`; `role: "user" \| "assistant"` |
|
|
33
|
+
| Case 3 | Inventory of the union | `export const STARCI_AI_CHAT_STATES: ReadonlyArray<StarCiAiChatState> = [ … ]` beside the type |
|
|
34
|
+
| Case 4 | Flag that is truly binary | `readonly isLoading?: boolean`, `readonly isPartial?: boolean` — `is`/`has` prefixed |
|
|
35
|
+
| Case 5 | Closed vocabulary in Grammar | `export type ButtonVariant = "primary" \| "secondary" \| "tertiary" \| "outline" \| "ghost"`; `const VARIANTS = { … } as const` |
|
|
36
|
+
|
|
37
|
+
## FE-TYPING-4 — Arrays
|
|
38
|
+
|
|
39
|
+
| Case | When | Write |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| Case 1 | Any array type | `ReadonlyArray<string>`, `Array<T>` — never `T[]` (lint `@typescript-eslint/array-type: generic`) |
|
|
42
|
+
| Case 2 | Frozen literal | `const PENDING_TURN_IDS = ["pending-1", "pending-2"] as const` (252 `as const` sites in `src/`) |
|
|
43
|
+
|
|
44
|
+
## FE-TYPING-5 — Inferred and declared
|
|
45
|
+
|
|
46
|
+
| Case | When | Write |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| Case 1 | Component return | inferred; no `: JSX.Element` on any of the 441 components |
|
|
49
|
+
| Case 2 | Helper return | declared when primitive: `(state: StarCiAiChatState): boolean =>`, `(turn: StarCiAiTurn, partialLabel: string): string =>` |
|
|
50
|
+
| Case 3 | Hook return | inferred from `useSWR<CourseDetail \| null>(…)`; the generic names the data |
|
|
51
|
+
| Case 4 | Async utility | declared: `export const runGraphQLWithToast = async <T>(action: () => Promise<GraphQLResponse<T>>, options: RunGraphQLWithToastOptions = {}): Promise<boolean>` |
|
|
52
|
+
| Case 5 | Narrowed literal | `return { mode: "signIn" as const, step: "code" as const, measure: "form" as const }` so a switch returns a union, not `string` |
|
|
53
|
+
|
|
54
|
+
## FE-TYPING-6 — Casting
|
|
55
|
+
|
|
56
|
+
| Case | When | Write |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Case 1 | Double cast | not written (lint `no-double-cast`) |
|
|
59
|
+
| Case 2 | Spec mock slot | `data: undefined as unknown` inside `vi.hoisted(() => ({ … }))` |
|
|
60
|
+
| Case 3 | Ignoring a prop | `void props` rather than an underscore or a cast |
|
|
61
|
+
|
|
62
|
+
## FE-TYPING-7 — Enums
|
|
63
|
+
|
|
64
|
+
| Case | When | Write |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| Case 1 | Component vocabulary | never an enum; a string-literal union |
|
|
67
|
+
| Case 2 | GraphQL operation names | `export enum MutationCvBlocks { Create = "create", … }` — the 92 enums in `src/` are all in `modules/api/graphql` |
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Kiểu
|
|
2
|
+
|
|
3
|
+
Tệp này trả lời một câu hỏi: cho một giá trị frontend, kiểu của nó được khai báo thế nào?
|
|
4
|
+
|
|
5
|
+
Nguồn: `tsconfig.json` (`strict: true`), `eslint.config.mjs` (`@typescript-eslint/array-type`
|
|
6
|
+
dạng generic), `src/components/**`, `src/hooks/swr/*`, `src/modules/toast/api.ts`,
|
|
7
|
+
`packages/grammar/src/common/conformance.ts`, `core/primitive/Button/index.tsx`.
|
|
8
|
+
|
|
9
|
+
## FE-TYPING-1 — `type` cho hình dạng, `interface` ở tầng hook
|
|
10
|
+
|
|
11
|
+
| Case | Dùng khi | Viết |
|
|
12
|
+
| --- | --- | --- |
|
|
13
|
+
| Case 1 | Props và hình dạng dữ liệu | `export type StarCiAiTurn = { readonly id: string; readonly role: "user" \| "assistant"; … }` (919 `export type X = {` so với 232 `export interface` trong `src/`, 403 so với 6 cho `…Props`) |
|
|
14
|
+
| Case 2 | Tham số hook | `export interface UseQueryCourseSwrParams { displayId?: string }` — phần `interface` tập trung ở `hooks/` và `modules/api`, không ở component |
|
|
15
|
+
| Case 3 | Giao | `export type CourseAdvisorRecommendationCardData = CourseAdvisorRecommendation & { readonly title?: string; … }` |
|
|
16
|
+
| Case 4 | Gói Grammar | `export type ButtonProps = { … }`, `export type GrammarRuleConformance = { … }` — `type` xuyên suốt |
|
|
17
|
+
|
|
18
|
+
## FE-TYPING-2 — `readonly` trên mọi trường
|
|
19
|
+
|
|
20
|
+
| Case | Dùng khi | Viết |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| Case 1 | Trường props | `readonly measure?: "form" \| "formCompact"` (2336 dòng prop `readonly` so với 69 dòng không) |
|
|
23
|
+
| Case 2 | Object lồng | `readonly action: { readonly href: string; readonly label: string }` |
|
|
24
|
+
| Case 3 | Tập hợp | `readonly turns: ReadonlyArray<StarCiAiTurn>`, `readonly states: Readonly<Record<StarCiAiChatState, string>>` |
|
|
25
|
+
| Case 4 | Khóa dạng tuple | `_key: readonly [string, string]` |
|
|
26
|
+
|
|
27
|
+
## FE-TYPING-3 — Union literal, không phải boolean, cho trạng thái
|
|
28
|
+
|
|
29
|
+
| Case | Dùng khi | Viết |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| Case 1 | Trạng thái component | `export type StarCiAiChatState = \| "sessionsPending" \| "sessionsFailed" \| "noSession" \| … \| "contextCleared"` (21 thành viên) |
|
|
32
|
+
| Case 2 | Chế độ | `export type StarCiAiMode = "general" \| "history"`; `role: "user" \| "assistant"` |
|
|
33
|
+
| Case 3 | Kho của union | `export const STARCI_AI_CHAT_STATES: ReadonlyArray<StarCiAiChatState> = [ … ]` kề bên kiểu |
|
|
34
|
+
| Case 4 | Cờ thực sự nhị phân | `readonly isLoading?: boolean`, `readonly isPartial?: boolean` — tiền tố `is`/`has` |
|
|
35
|
+
| Case 5 | Từ vựng đóng trong Grammar | `export type ButtonVariant = "primary" \| "secondary" \| "tertiary" \| "outline" \| "ghost"`; `const VARIANTS = { … } as const` |
|
|
36
|
+
|
|
37
|
+
## FE-TYPING-4 — Mảng
|
|
38
|
+
|
|
39
|
+
| Case | Dùng khi | Viết |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| Case 1 | Mọi kiểu mảng | `ReadonlyArray<string>`, `Array<T>` — không bao giờ `T[]` (lint `@typescript-eslint/array-type: generic`) |
|
|
42
|
+
| Case 2 | Literal đóng băng | `const PENDING_TURN_IDS = ["pending-1", "pending-2"] as const` (252 chỗ `as const` trong `src/`) |
|
|
43
|
+
|
|
44
|
+
## FE-TYPING-5 — Suy luận và khai báo
|
|
45
|
+
|
|
46
|
+
| Case | Dùng khi | Viết |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| Case 1 | Kiểu trả về của component | suy luận; không có `: JSX.Element` trên bất kỳ component nào trong 441 |
|
|
49
|
+
| Case 2 | Kiểu trả về của helper | khai báo khi là nguyên thủy: `(state: StarCiAiChatState): boolean =>`, `(turn: StarCiAiTurn, partialLabel: string): string =>` |
|
|
50
|
+
| Case 3 | Kiểu trả về của hook | suy luận từ `useSWR<CourseDetail \| null>(…)`; generic gọi tên dữ liệu |
|
|
51
|
+
| Case 4 | Tiện ích async | khai báo: `export const runGraphQLWithToast = async <T>(action: () => Promise<GraphQLResponse<T>>, options: RunGraphQLWithToastOptions = {}): Promise<boolean>` |
|
|
52
|
+
| Case 5 | Literal thu hẹp | `return { mode: "signIn" as const, step: "code" as const, measure: "form" as const }` để một switch trả về union, không phải `string` |
|
|
53
|
+
|
|
54
|
+
## FE-TYPING-6 — Ép kiểu
|
|
55
|
+
|
|
56
|
+
| Case | Dùng khi | Viết |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| Case 1 | Ép kép | không viết (lint `no-double-cast`) |
|
|
59
|
+
| Case 2 | Ô giả lập trong spec | `data: undefined as unknown` bên trong `vi.hoisted(() => ({ … }))` |
|
|
60
|
+
| Case 3 | Bỏ qua một prop | `void props` thay vì gạch dưới hay ép kiểu |
|
|
61
|
+
|
|
62
|
+
## FE-TYPING-7 — Enum
|
|
63
|
+
|
|
64
|
+
| Case | Dùng khi | Viết |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| Case 1 | Từ vựng component | không bao giờ là enum; là union chuỗi literal |
|
|
67
|
+
| Case 2 | Tên thao tác GraphQL | `export enum MutationCvBlocks { Create = "create", … }` — 92 enum trong `src/` đều nằm ở `modules/api/graphql` |
|