create-flowdular 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
- package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.agents/skills/variables/SKILL.md +156 -0
- package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/.ai/README.md +62 -0
- package/agent-template/.ai/agents/README.md +27 -0
- package/agent-template/.ai/agents/module-executor.md +36 -0
- package/agent-template/.ai/agents/reviewer.md +23 -0
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
- package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
- package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
- package/agent-template/.ai/agents/spec-author.md +29 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
- package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
- package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
- package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
- package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
- package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
- package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
- package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
- package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
- package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
- package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
- package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
- package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
- package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
- package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
- package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
- package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
- package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
- package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
- package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
- package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
- package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
- package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
- package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
- package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
- package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
- package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
- package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
- package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
- package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
- package/agent-template/.ai/blueprints/new-module/README.md +7 -0
- package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
- package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
- package/agent-template/.ai/blueprints/release/README.md +5 -0
- package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
- package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
- package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
- package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
- package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
- package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
- package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
- package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
- package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
- package/agent-template/.ai/blueprints/security-review/README.md +5 -0
- package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
- package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
- package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
- package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
- package/agent-template/.ai/examples/README.md +8 -0
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
- package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
- package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
- package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
- package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
- package/agent-template/.ai/examples/client-contribution/README.md +11 -0
- package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
- package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
- package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
- package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
- package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
- package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
- package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
- package/agent-template/.ai/guides/application-development.md +97 -0
- package/agent-template/.ai/policies/capabilities.yaml +164 -0
- package/agent-template/.ai/policies/model-routing.yaml +72 -0
- package/agent-template/.ai/policies/path-ownership.yaml +65 -0
- package/agent-template/.ai/policies/task-budgets.yaml +37 -0
- package/agent-template/.ai/references/catalog/LICENSE +21 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
- package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
- package/agent-template/.ai/references/catalog/module.json +27 -0
- package/agent-template/.ai/references/catalog/package.json +49 -0
- package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
- package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
- package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
- package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
- package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
- package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
- package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
- package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
- package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
- package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
- package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
- package/agent-template/.ai/references/catalog/src/index.ts +31 -0
- package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
- package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
- package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
- package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
- package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
- package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
- package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
- package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
- package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
- package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
- package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
- package/agent-template/.ai/references/catalog/translations/en.json +101 -0
- package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
- package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
- package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
- package/agent-template/.ai/references/catalog.provenance.json +55 -0
- package/agent-template/.ai/rules/flowdular.md +86 -0
- package/agent-template/.ai/skills/README.md +36 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
- package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
- package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
- package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
- package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
- package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
- package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
- package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
- package/agent-template/.ai/skills/variables/SKILL.md +164 -0
- package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
- package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.claude/skills/variables/SKILL.md +156 -0
- package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/AGENTS.md +77 -0
- package/agent-template/CLAUDE.md +77 -0
- package/agent-template/docs/adr/0001-development-reload.md +16 -0
- package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
- package/agent-template/docs/adr/0003-module-settings.md +22 -0
- package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
- package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
- package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
- package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
- package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
- package/agent-template/docs/agent-contract.md +45 -0
- package/agent-template/docs/configuration.md +122 -0
- package/agent-template/docs/database-adapters.md +346 -0
- package/agent-template/docs/design-system.md +217 -0
- package/agent-template/docs/modules.md +146 -0
- package/agent-template/platform/scripts/build.mjs +38 -0
- package/agent-template/rulesync.jsonc +11 -0
- package/dist/bin.js +40 -2
- package/package.json +3 -2
- package/template/default/.prettierignore +9 -0
- package/template/default/README.md +12 -0
- package/template/default/flowdular.json +3 -3
- package/template/default/modules/example/package.json +2 -2
- package/template/default/package.json +6 -2
- package/template/default/platform/octane.config.ts +17 -6
- package/template/default/platform/package.json +3 -2
- package/template/default/pnpm-workspace.yaml +1 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: perf-audit
|
|
3
|
+
description: >-
|
|
4
|
+
Find the hot paths of a module or platform package, state their cost, and
|
|
5
|
+
change only what a measurement justifies.
|
|
6
|
+
roles:
|
|
7
|
+
- backend-engineer
|
|
8
|
+
- frontend-engineer
|
|
9
|
+
- module-executor
|
|
10
|
+
- reviewer
|
|
11
|
+
when: A screen or endpoint is slow, a list grows, or a review asks whether the change scales.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Performance audit
|
|
15
|
+
|
|
16
|
+
Measure first. A micro-rewrite without a number is not a performance change and does not belong in the diff.
|
|
17
|
+
|
|
18
|
+
## 1. Inventory the hot paths
|
|
19
|
+
|
|
20
|
+
Server (per request):
|
|
21
|
+
|
|
22
|
+
- Queries are asynchronous and pooled, so the cost is round trips, not a blocked event loop. A query inside a loop, an N+1 read after a list, or one transaction per row multiplies the round trip by the row count; do the work in one statement. A list endpoint that returns a tenant's whole table is still O(rows) per request, so paginate or filter in SQL, never in JavaScript after the rows arrive.
|
|
23
|
+
- A lease or a transaction held longer than the work needs starves the pool. Open the transaction around the statements it protects and release it; never hold one across a fetch, an agent call, or a sleep.
|
|
24
|
+
- `list(tenantId)` orders by a column: the index must cover `(tenant_id, <order column>, id)` (`.ai/references/catalog/src/services/migration.ts` has `catalog_items_tenant_sku_idx`). Without it PostgreSQL adds a sort node over the tenant's rows on every call.
|
|
25
|
+
- `readJsonObject` caps bodies at 16 KB and reads the whole text once; do not raise the cap for one field, add an endpoint.
|
|
26
|
+
- `defineEndpoint` allocates a request id and a Set of permissions per request through `endpointIdentityFromContext` (`new Set(principal.scopes)`); this is fine at current sizes and not a target.
|
|
27
|
+
- The runtime checks the migration ledger once, behind a short `purpose: 'migration'` lease, and then holds one runtime lease for the repository (`src/server/runtime.ts` shares a single initialization promise). Acquiring a lease or building a repository per request adds a ledger read and a pool acquisition to every request.
|
|
28
|
+
|
|
29
|
+
Client (per render):
|
|
30
|
+
|
|
31
|
+
- `items.filter(...)` and `toLocaleLowerCase` run on every render in `CatalogView.tsrx`. With a few hundred rows this is invisible; past that, derive once with `store.derive((get) => ...)` from `segment-state` or filter in the effect that loads data.
|
|
32
|
+
- One store per component (`useMemo(() => createXClientState(), [])`) is the intended shape; a shared module-level store would leak between screens.
|
|
33
|
+
- Widgets in `dashboard.metrics` each fetch on mount. Five widgets are five requests on the dashboard; a widget that needs a count should not load the whole list once an endpoint can count.
|
|
34
|
+
|
|
35
|
+
Bundle:
|
|
36
|
+
|
|
37
|
+
- `packages/ui/src/index.ts` imports the Plex fonts and `styles/index.css` at the top, so every consumer of `@flowdular/sdk/ui` pulls them once. A module must not import fonts or global CSS again.
|
|
38
|
+
- Module CSS is allowed only for module-specific composites (`modules/agents/src/client/agents.css`).
|
|
39
|
+
|
|
40
|
+
Agent runtime (`modules/agents`, `packages/harness`):
|
|
41
|
+
|
|
42
|
+
- Bounds that exist: `maxSteps` 1 to 32 and `timeoutMs` 250 to 86400000 per agent definition (`modules/agents/src/services/agent-service.ts`), worker concurrency `FD_AGENT_WORKER_CONCURRENCY` (1 to 16, default 2) and lease `FD_AGENT_WORKER_LEASE_MS` (`modules/agents/src/server/runtime.ts`).
|
|
43
|
+
- Tool calls have a deadline (`timeoutMs`, default 30 seconds, bounded from 250 to 600000 ms) and serialized output is capped at 32 KB by the harness. A list tool must still page or limit rows so useful data fits inside that cap.
|
|
44
|
+
|
|
45
|
+
## 2. Measure
|
|
46
|
+
|
|
47
|
+
- Server: a vitest `bench` or a script against the module's `tests/support/database.ts` provider seeded with 10k rows for one tenant and 10k for another; time `list(tenantId)` before and after an index. `await database.query({ text: 'EXPLAIN (ANALYZE, BUFFERS) SELECT ...' })` shows an `Index Scan` or the `Seq Scan` plus `Sort` pair that means the index is not covering the order.
|
|
48
|
+
- Client: count renders with a counter in the component during development, or `store.stats()` for commit counts. Remove the instrumentation before the handoff.
|
|
49
|
+
- Bundle: `pnpm --filter @flowdular/platform build` prints chunk sizes.
|
|
50
|
+
|
|
51
|
+
Record the number, the input size and the machine in the handoff or PR body.
|
|
52
|
+
|
|
53
|
+
## 2b. Bench recipe
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
// tests/list.bench.ts (vitest bench; run with: pnpm --filter @flowdular/module-catalog exec vitest bench)
|
|
57
|
+
import { bench, describe } from 'vitest';
|
|
58
|
+
import { CatalogService } from '../src/services/catalog-service.ts';
|
|
59
|
+
import { createCatalogTestDatabase } from './support/database.ts';
|
|
60
|
+
|
|
61
|
+
const database = await createCatalogTestDatabase();
|
|
62
|
+
const service = new CatalogService(database.repository);
|
|
63
|
+
for (let index = 0; index < 10_000; index += 1) {
|
|
64
|
+
for (const tenant of ['tenant-a', 'tenant-b']) {
|
|
65
|
+
await service.create(tenant, {
|
|
66
|
+
sku: `SKU-${index}`,
|
|
67
|
+
name: `Item ${index}`,
|
|
68
|
+
kind: 'product',
|
|
69
|
+
unit: 'each',
|
|
70
|
+
basePriceMinor: index,
|
|
71
|
+
currency: 'EUR',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('catalog list', () => {
|
|
77
|
+
bench('list one tenant (10k of 20k rows)', async () => {
|
|
78
|
+
await service.list('tenant-a');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Keep bench files out of `tests/**/*.test.ts` so the `tests` gate does not run them; name them `*.bench.ts`. Delete the file or keep it only when the number is worth tracking.
|
|
84
|
+
|
|
85
|
+
## 2c. Report template
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
Path: GET /api/catalog/items -> CatalogService.list -> DatabaseCatalogRepository.list
|
|
89
|
+
Complexity: O(rows of tenant) time and space per request; ORDER BY covered by catalog_items_tenant_sku_idx
|
|
90
|
+
Measurement: 10k rows per tenant, embedded PGlite, Node 24: 3.1 ms per call before, 3.0 ms after (no change)
|
|
91
|
+
Decision: no code change; add pagination when a tenant exceeds ~50k items
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 3. Change only what the number justifies
|
|
95
|
+
|
|
96
|
+
Allowed without a benchmark: adding a missing covering index; moving a filter from JavaScript into the SQL `WHERE`; removing a duplicate fetch. Everything else (loop style, hoisting, memoization of cheap values, replacing `Array.prototype` calls) needs a before and after measurement in the same environment.
|
|
97
|
+
|
|
98
|
+
Complexity to state in the review: for each new data structure and loop on a request or render path, its time and space in terms of rows, tenants, or items. Unbounded growth (a Map keyed by tenant that is never pruned, a list of listeners never detached) is a defect even when each entry is small.
|
|
99
|
+
|
|
100
|
+
## Pitfalls
|
|
101
|
+
|
|
102
|
+
- `LIKE` or `=` against `lower(column)` cannot use a plain `(tenant_id, column)` index; store a normalized column (`sku_normalized`) as `.ai/references/catalog` does, or add an expression index on `lower(column)`.
|
|
103
|
+
- `ORDER BY lower(name)` (`Flowdular/official-modules`, `modules/parties`) cannot use the `(tenant_id, name, id)` index for the sort; acceptable at current sizes, name it if parties grow.
|
|
104
|
+
- A `Kpi` that shows `items.length` after loading the full list is O(rows) network per dashboard load.
|
|
105
|
+
- Never change behaviour in a performance commit; keep the functional tests green and add none that assert internal call counts.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release-eject-pr
|
|
3
|
+
description: >-
|
|
4
|
+
Land a module or core change: the sandbox eject sequence, the repository
|
|
5
|
+
verification gates, the git branch and PR conventions, and the post-merge
|
|
6
|
+
scope grant.
|
|
7
|
+
roles:
|
|
8
|
+
- module-executor
|
|
9
|
+
- reviewer
|
|
10
|
+
- backend-engineer
|
|
11
|
+
when: A change is ready to leave a sandbox session or a working tree and reach the platform.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Eject, verify, deliver
|
|
15
|
+
|
|
16
|
+
Two paths reach the same place. The sandbox path is chat, gates, preview, eject. The direct path is a skill in your own coding tool, `pnpm verify`, a pull request. Both end with the module enabled through the CLI and its scopes granted.
|
|
17
|
+
|
|
18
|
+
## 1. Sandbox eject (`packages/sandbox/src/server/delivery/{local,steps}.ts`, `routes.ts`)
|
|
19
|
+
|
|
20
|
+
`POST /sandbox/api/sessions/:id/eject` with `{ apply: false }` returns the plan: for every module of the session the files that land in `modules/<dir>`, the files that would be overwritten, the files the session deleted and the eject will remove, packages a module declares that the workspace cannot resolve yet, the gates, and whether the connected application has to restart. With `{ apply: true }` the sandbox streams the steps:
|
|
21
|
+
|
|
22
|
+
1. Gates: `spec-schema` and `module-schema` once, then `dependencies`, `typecheck`, `tests`, `format` per draft module. Any failure stops the eject before a file is written (`EJECT_GATES_FAILED`).
|
|
23
|
+
2. Copy of each session module into `modules/<dir>`, then removal of the files an edit deleted (`removeModuleFiles`, empty directories included).
|
|
24
|
+
3. `pnpm install` at the workspace root.
|
|
25
|
+
4. `pnpm flowdular module enable <id> --apply --json` for each new module (writes `flowdular.json`, `platform/package.json`, `platform/src/generated/*`, and grants the module's scopes itself).
|
|
26
|
+
5. `pnpm flowdular auth sync-scopes --module <id> --apply --json` for each module (idempotent re-grant, needed for edited modules that added a permission).
|
|
27
|
+
6. `pnpm --filter @flowdular/platform typecheck`.
|
|
28
|
+
7. Optionally `pnpm build`.
|
|
29
|
+
8. A restart note: the connected application loads the new composition and runs new schema constants only at start, so a local `pnpm dev` restarts and a remote deployment redeploys.
|
|
30
|
+
|
|
31
|
+
A failing step stops the delivery there with the step's output; the session is marked delivered only when every step passed. Eject requires the connected grant to hold `sandbox.modules.eject`. Delivery targets sit behind one interface (`delivery/types.ts`); the request names one with `target: 'workspace' | 'git-pr'` (default from `flowdular.json`), `workspace` is the one above, `git-pr` is section 2.
|
|
32
|
+
|
|
33
|
+
## 2. Git delivery from a sandbox (`target: 'git-pr'`, `packages/sandbox/src/server/delivery/git-pr.ts`)
|
|
34
|
+
|
|
35
|
+
The pull request is the unit of a delivery: one session, one branch, one PR, every module the session touched. Nothing in the operator's working tree or index changes; the work happens in a detached worktree under `.flowdular/sandbox/worktrees/<session>` that is removed afterwards, whatever the outcome.
|
|
36
|
+
|
|
37
|
+
- Available when the workspace is a git work tree with at least one commit, `.flowdular/` is ignored, and the configured remote exists (`git rev-parse --verify HEAD`, `git remote get-url <remote>`); a repository without commits answers "make the first commit before delivering as a pull request". A PR is opened when `gh auth status` succeeds (a provider token sealed in the sandbox configuration is handed to gh as `GH_TOKEN`); otherwise the branch is pushed and the compare link shown.
|
|
38
|
+
- Branch `<branchPrefix>/<module-dir>-<session id first 8>` from `<remote>/<baseBranch>`: `git fetch`, `git worktree add --detach`, `git switch -C`.
|
|
39
|
+
- In the worktree: the copy and the removals, `pnpm install --offline` (fallback `--prefer-offline`), `pnpm flowdular module enable <id> --apply` for each new module with the worktree as `--dir`, the platform typecheck.
|
|
40
|
+
- Guardrails before the commit: `git status --porcelain` in the worktree may list only `modules/<dir>/**` of the session's modules and `pnpm-lock.yaml`. A delivery with a new module may also change `flowdular.json`, `platform/package.json` and `platform/src/generated/**`. The count stays within `sandbox.delivery.maxChangedFiles` or, unset, the `.ai/policies/task-budgets.yaml` figure for the session kind (`new-module` 30, `edit-module` 12, default 18); new packages within `maxNewDependencies` (0). Owners come from `.ai/policies/path-ownership.yaml`; with `crossOwnerChanges.requireReviewer` a cross-owner change asks for a reviewer from each owner in the body. A violation lists the offending paths and stops before anything is committed; the branch is deleted.
|
|
41
|
+
- Commit `sandbox: add|update <module id>` (author from git config) with the session id and the gate summary, `git push -u --force-with-lease <remote> <branch>`, `gh pr create --base <baseBranch> --head <branch> --title "Add|Update <module id>" --body-file <tmp>` (`--reviewer` from `git.reviewers`). A second delivery of the same session updates the branch and keeps the open PR.
|
|
42
|
+
- PR body, plain: two or three sentences from the brief and the last review handoff, `Session <id>.`, the gate table (gate, module, result), the file list grouped as added, modified, removed, `Post-merge: pnpm flowdular auth sync-scopes --module <id> --apply` per module, the reviewer note. No attribution footers, no dashes.
|
|
43
|
+
- `sync-scopes` does not run in the worktree: it is a runtime action against the deployment database, so it stays the post-merge step. Deploy, run it with `FD_AUTH_DATABASE` pointing at that database, verify the navigation entry appears for an owner.
|
|
44
|
+
- Configuration in `flowdular.json`, all optional and validated by `packages/contracts/schemas/project.schema.json`: `sandbox.delivery { default: 'workspace' | 'git-pr', targets: ['workspace', 'git-pr'], git: { remote: 'origin', baseBranch: 'main', branchPrefix: 'sandbox', provider: 'github' | 'none', mode: 'auto' | 'direct' | 'fork', forkOwner: null, reviewers: [] }, maxChangedFiles }`. Read at request time. `auto` never creates a fork: it uses direct delivery only after GitHub confirms push access and otherwise asks the operator to choose `direct` or `fork`. Only an explicit `fork` choice authorizes fork creation.
|
|
45
|
+
- The screen: "Into this workspace" / "As a pull request", offered only when both are usable here; an unusable target says why. The git plan shows branch, base, changed files against the budget, new packages, owners touched and the guardrail verdict; done shows the PR or compare link. `.flowdular/sandbox/sessions/<id>/delivery.json` keeps the branch and the URL.
|
|
46
|
+
|
|
47
|
+
## 3. Direct path from a working tree
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm flowdular module enable <id> --apply # new module only; also grants its scopes (result: scopes)
|
|
51
|
+
pnpm flowdular auth sync-scopes --module <id> --apply # re-grant after a new permission, or against another database
|
|
52
|
+
pnpm verify # typecheck, test, validate, format:check
|
|
53
|
+
pnpm build # cli build and smoke, module sync --apply, platform build
|
|
54
|
+
pnpm audit --prod --audit-level high # what CI runs (.github/workflows/ci.yml)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`pnpm validate` runs `spec validate --all`, `blueprint validate --all` (every `.ai/blueprints/*/blueprint.json` plus its companion files) and `module validate`. It checks manifests and schemas, not behaviour; typecheck and tests are the evidence.
|
|
58
|
+
|
|
59
|
+
Branch names: `feat/<module>-<topic>`, `fix/<module>-<topic>`, `core/<package>-<topic>`. Commit one logical change per commit; generated files travel with the command that produced them.
|
|
60
|
+
|
|
61
|
+
## 4. PR conventions (repository rules)
|
|
62
|
+
|
|
63
|
+
- Short body: what changed and why in a few sentences, gotchas, one line on verification (`pnpm verify passes; pnpm build passes`). No file tables, no design essays, no restating the diff.
|
|
64
|
+
- No AI attribution: no AI `Co-Authored-By` line and no `Generated with` footer.
|
|
65
|
+
- No em or en dashes anywhere in commits, PR titles or bodies.
|
|
66
|
+
- Generated files and `modules.enabled` change only through the CLI, and the PR says which command produced them.
|
|
67
|
+
- Changes to `packages/**` name the consumers that were migrated (`core-extend`).
|
|
68
|
+
|
|
69
|
+
## 4b. Pull request body template
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
Adds inventory.core: tenant-scoped stock locations with read and manage scopes,
|
|
73
|
+
a list and create endpoint, a Locations screen with a drawer form, and a
|
|
74
|
+
dashboard KPI. Covers INVENTORY-LIST, INVENTORY-CREATE, INVENTORY-DENY,
|
|
75
|
+
INVENTORY-ISOLATION.
|
|
76
|
+
|
|
77
|
+
Generated by the CLI in this PR: flowdular.json and platform/package.json
|
|
78
|
+
(pnpm flowdular module enable inventory.core --apply), platform/src/generated/*
|
|
79
|
+
(module sync), pnpm-lock.yaml (pnpm install).
|
|
80
|
+
|
|
81
|
+
Gates: spec-schema, module-schema, dependencies, typecheck, tests (7), format
|
|
82
|
+
all passed in the sandbox eject; pnpm verify and pnpm build pass locally.
|
|
83
|
+
|
|
84
|
+
Post-merge: pnpm flowdular auth sync-scopes --module inventory.core --apply against
|
|
85
|
+
the deployment database.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 4c. Pre-flight checklist
|
|
89
|
+
|
|
90
|
+
- `git status` shows only `modules/<dir>/**` plus the CLI-generated files named above.
|
|
91
|
+
- `module.json` `version`, `spec/module.yaml` `specVersion` and `package.json` `version` are equal.
|
|
92
|
+
- `spec/module.yaml` is `approved`; the PR does not change its status.
|
|
93
|
+
- No `console.log` left in module code; no secrets or tokens in tests.
|
|
94
|
+
- The PR title is under 70 characters and names the module (`inventory.core: stock locations`).
|
|
95
|
+
|
|
96
|
+
## 5. Container and tags
|
|
97
|
+
|
|
98
|
+
CI builds the image from `infra/docker/Dockerfile` on every PR (no push). A release tag `v*.*.*` is the trigger for publishing (workflow owned by the platform team). The image runs `node platform/dist/server/entry.js` with `/data` as the database volume. Compose and Kubernetes provide `FD_AUTH_DATABASE`, `FD_AGENTS_DATABASE`, and `FD_WORKFLOWS_DATABASE`. Production also requires `FD_AGENT_CREDENTIAL_KEY`, `FD_AGENT_RUN_GRANT_KEY`, `FD_WORKFLOWS_PAYLOAD_KEY`, and `FD_WORKFLOWS_CURSOR_KEY`; generate every key independently with `openssl rand -base64 32` and supply it through the deployment secret.
|
|
99
|
+
|
|
100
|
+
## Pitfalls
|
|
101
|
+
|
|
102
|
+
- An eject removes the files a session deleted; a rename shows up as one removal and one addition in the plan.
|
|
103
|
+
- `module enable` runs `pnpm install` when the package is not linked; a failing install is reported as `pnpm install failed while linking the module package`. A failed scope grant after a successful enable is `MODULE_SCOPES_SYNC_FAILED`; rerun `auth sync-scopes`.
|
|
104
|
+
- `platform/.generated/` is a stale ignore entry; the live generated directory is `platform/src/generated/`.
|
|
105
|
+
- `pnpm flowdular module sync --apply` is also run by `pnpm dev` and `pnpm build`; a dirty generated file after a checkout means the enabled list and the files disagree.
|
|
106
|
+
|
|
107
|
+
## Required auto-review
|
|
108
|
+
|
|
109
|
+
Before delivery, complete the separate `auto-review` phase. Sandbox eject requires
|
|
110
|
+
a current per-module review record and passing schema, dependency, typecheck,
|
|
111
|
+
test and format gates. Missing, skipped and empty-suite results block delivery.
|
|
112
|
+
Any module edit invalidates its review. Host changes also need the auto-review
|
|
113
|
+
report and full verification described by that skill before completion.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-approval
|
|
3
|
+
description: >-
|
|
4
|
+
Apply an explicit user approval to the exact current Flowdular module
|
|
5
|
+
specification. Use only when the user directly asks to approve one or more
|
|
6
|
+
named current specs, never to infer or initiate approval.
|
|
7
|
+
roles:
|
|
8
|
+
- spec-author
|
|
9
|
+
- module-executor
|
|
10
|
+
- reviewer
|
|
11
|
+
when: The user explicitly says to approve a specific module specification or every current specification in a named sandbox session.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Approve a module specification
|
|
15
|
+
|
|
16
|
+
Approval is a user decision that an agent may record only as a mechanical
|
|
17
|
+
delegate. Never decide that a specification is good enough, treat a review
|
|
18
|
+
verdict as approval, or infer approval from requests such as "continue", "looks
|
|
19
|
+
good", or "build it".
|
|
20
|
+
|
|
21
|
+
## 1. Required authority
|
|
22
|
+
|
|
23
|
+
Proceed only when the current user message explicitly approves:
|
|
24
|
+
|
|
25
|
+
- one named module;
|
|
26
|
+
- the clearly active module referred to as "this module"; or
|
|
27
|
+
- every current module in one named sandbox session.
|
|
28
|
+
|
|
29
|
+
The instruction must refer to the current specification. An approval copied from
|
|
30
|
+
an earlier conversation, a different hash, or an earlier session is not
|
|
31
|
+
authority for changed content.
|
|
32
|
+
|
|
33
|
+
If the target is ambiguous, ask which module. If the user approves several
|
|
34
|
+
modules, process and report each separately.
|
|
35
|
+
|
|
36
|
+
## 2. Review the exact input
|
|
37
|
+
|
|
38
|
+
Before recording approval:
|
|
39
|
+
|
|
40
|
+
1. Read the entire spec/module.yaml.
|
|
41
|
+
2. Confirm its module id and current specVersion.
|
|
42
|
+
3. Run pnpm flowdular spec validate --all --json.
|
|
43
|
+
4. Check the current diff or sandbox review for the requirements, permissions,
|
|
44
|
+
data ownership and acceptance scenarios being approved.
|
|
45
|
+
5. Stop if validation fails, the module cannot be resolved, or the spec changed
|
|
46
|
+
while it was being reviewed.
|
|
47
|
+
|
|
48
|
+
Do not rewrite requirements while applying approval. A requested content change
|
|
49
|
+
is a new spec-authoring step and needs approval after that edit.
|
|
50
|
+
|
|
51
|
+
## 3. Sandbox path
|
|
52
|
+
|
|
53
|
+
In the sandbox, use the operator approval action for the selected session module.
|
|
54
|
+
The live route is POST /sandbox/api/sessions/:id/approve, exposed by
|
|
55
|
+
approveSpecification in packages/sandbox/src/client/api.ts.
|
|
56
|
+
|
|
57
|
+
The route changes the status presentation and records the SHA-256 hash of the
|
|
58
|
+
exact approved text in the session. Do not patch the session workspace file to
|
|
59
|
+
bypass that route. Do not forge browser cookies or sandbox request headers. If
|
|
60
|
+
the operator route is unavailable, report the blocker and leave the spec
|
|
61
|
+
unapproved.
|
|
62
|
+
|
|
63
|
+
A multi-module session requires an approval record for every affected module.
|
|
64
|
+
Approving one module does not unblock another.
|
|
65
|
+
|
|
66
|
+
## 4. Repository checkout path
|
|
67
|
+
|
|
68
|
+
Outside the sandbox, after the explicit current user instruction:
|
|
69
|
+
|
|
70
|
+
1. Change only the top-level status value to approved.
|
|
71
|
+
2. Format the file without changing its requirements.
|
|
72
|
+
3. Run pnpm flowdular spec validate --all --json again.
|
|
73
|
+
4. Compute shasum -a 256 modules/<dir>/spec/module.yaml.
|
|
74
|
+
5. Report the module id, version and exact approved hash.
|
|
75
|
+
|
|
76
|
+
Do not combine approval with implementation changes in the same edit. Once the
|
|
77
|
+
approved state and hash are reported, implementation follows module-new or
|
|
78
|
+
module-update.
|
|
79
|
+
|
|
80
|
+
## 5. Staleness
|
|
81
|
+
|
|
82
|
+
Approval applies only to the exact content that was approved.
|
|
83
|
+
|
|
84
|
+
- In a sandbox session, the recorded hash is authoritative. Any later edit,
|
|
85
|
+
request for changes, or added module reopens the approval gate.
|
|
86
|
+
- In a checkout, any later requirement change must return the status to draft
|
|
87
|
+
or in-review before authoring continues, then receive a new explicit user
|
|
88
|
+
approval.
|
|
89
|
+
- A version bump alone is still a content change and needs fresh approval.
|
|
90
|
+
- Never copy an approved status line into another module or session.
|
|
91
|
+
|
|
92
|
+
## 6. Refuse
|
|
93
|
+
|
|
94
|
+
Refuse to approve when:
|
|
95
|
+
|
|
96
|
+
- no current user instruction explicitly grants approval;
|
|
97
|
+
- the user asked only for review, implementation or continuation;
|
|
98
|
+
- validation fails;
|
|
99
|
+
- unresolved business questions remain in the spec;
|
|
100
|
+
- the target module or session is ambiguous;
|
|
101
|
+
- the content changed after the user's decision;
|
|
102
|
+
- a sandbox role attempts to approve its own output.
|
|
103
|
+
|
|
104
|
+
A sandbox business manager may request approval in its handoff. That request is
|
|
105
|
+
not approval and cannot satisfy this skill's authority requirement.
|
|
106
|
+
|
|
107
|
+
## 7. Handoff
|
|
108
|
+
|
|
109
|
+
After approval, state exactly what was approved and which hash now represents
|
|
110
|
+
it. Do not claim that implementation or delivery also passed. Continue to
|
|
111
|
+
implementation only when the user's request includes it and the matching skill
|
|
112
|
+
allows it.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: test-hardening
|
|
3
|
+
description: >-
|
|
4
|
+
Make a module test suite prove behaviour: where tests live and run, the route
|
|
5
|
+
recipe, the embedded PostgreSQL provider, the denial and isolation cases every
|
|
6
|
+
endpoint needs, and the break-the-implementation check.
|
|
7
|
+
roles:
|
|
8
|
+
- backend-engineer
|
|
9
|
+
- frontend-engineer
|
|
10
|
+
- reviewer
|
|
11
|
+
- module-executor
|
|
12
|
+
when: A module has few or tautological tests, a bug escaped the suite, or a reviewer asks whether the tests guard the change.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Harden a test suite
|
|
16
|
+
|
|
17
|
+
## 1. Where tests live and run
|
|
18
|
+
|
|
19
|
+
- `tests/module.test.ts` (one file per module today; more files are fine). `tsconfig.json` includes `src/**/*` and `tests/**/*.ts`, so a test file is `.ts`; `.tsrx` components are not compiled by vitest here. Testable client logic (formatting, filtering, mapping, state transitions) goes into a `.ts` helper next to the view and is imported by the test.
|
|
20
|
+
- Runner: `vitest run` (`pnpm --filter @flowdular/module-<dir> test`). In the sandbox the `tests` gate runs `vitest run --passWithNoTests` inside the module directory, so a module with no tests passes the gate. Treat an empty or trivial suite as a defect, not a pass.
|
|
21
|
+
- New modules use vitest 4.1.11, typescript 5.9.3 and `@types/node` 24.13.3. The catalog reference is an immutable older release; use the current scaffold dependency versions for new code.
|
|
22
|
+
|
|
23
|
+
## 2. Repositories on an embedded PostgreSQL
|
|
24
|
+
|
|
25
|
+
`createPgliteTestProvider()` from `@flowdular/sdk/database-testing` runs a real PostgreSQL inside the test process, with the same `coreloom_runtime` and `coreloom_background` roles and the same forced row-level security a deployment enforces. Booting it costs about two seconds, so a suite opens one provider per test file, migrates it once, and truncates the module's tables between cases; `.ai/references/catalog/tests/support/database.ts` is the shape (`createCatalogTestDatabase` hands out a lease per fixture, `closeCatalogTestDatabases` runs in `afterAll`). Build the service on top: `new CatalogService((await createCatalogTestDatabase()).repository)`. A database module also keeps `tests/migrations.test.ts` for SQL byte parity, fresh apply, safe pre-ledger adoption, and a clean second start. A module whose spec has no `database` capability gets a `MemoryXRepository` from the scaffold instead; a module with a database tests the database repository, never a hand-written fake, because the SQL, the ledger and the row-level security are what need testing.
|
|
26
|
+
|
|
27
|
+
## 3. Route recipe (from `modules/auth/tests/endpoints.test.ts`)
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createContext } from '@octanejs/app-core';
|
|
31
|
+
import { createAuthenticationMiddleware } from '@flowdular/sdk/modules/auth/server';
|
|
32
|
+
// build an AuthRuntime around a DatabaseAuthRepository on a createPgliteTestProvider() lease and a cheap scrypt cost,
|
|
33
|
+
// sign up through the auth sign-up route to obtain a cookie and csrfToken, then:
|
|
34
|
+
const routes = createCatalogRoutes(auth, runtime);
|
|
35
|
+
const create = routes.find(
|
|
36
|
+
(route) =>
|
|
37
|
+
route.path === '/api/catalog/items' && route.methods.includes('POST'),
|
|
38
|
+
)!;
|
|
39
|
+
const response = await create.handler(
|
|
40
|
+
createContext(
|
|
41
|
+
new Request('https://erp.example/api/catalog/items', {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'content-type': 'application/json',
|
|
45
|
+
origin: 'https://erp.example',
|
|
46
|
+
cookie,
|
|
47
|
+
'x-csrf-token': csrfToken,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(input),
|
|
50
|
+
}),
|
|
51
|
+
{},
|
|
52
|
+
),
|
|
53
|
+
);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The auth middleware must have set the principal for `endpointIdentityFromContext` to find it: either run `auth.middleware(context, next)` before the handler or resolve the session and set `AUTH_PRINCIPAL_STATE_KEY` on `context.state` (both exported from `@flowdular/sdk/modules/auth/server`). Read `modules/auth/tests/endpoints.test.ts` for the runtime shape (`cookie`, `settings`, `service`, `middleware`).
|
|
57
|
+
|
|
58
|
+
## 4. Cases every endpoint needs
|
|
59
|
+
|
|
60
|
+
- 401 `UNAUTHENTICATED`: no cookie, no bearer token.
|
|
61
|
+
- 403 `FORBIDDEN`: a principal whose scopes lack the permission.
|
|
62
|
+
- 403 on a mutation without `x-csrf-token` (`CSRF_REJECTED`) and without `origin` (`ORIGIN_REQUIRED`).
|
|
63
|
+
- 400 with the stable code for each validation bound (`INVALID_INPUT`, module codes such as `INVALID_ITEM_KIND`).
|
|
64
|
+
- 409 for the tenant-scoped uniqueness rule, and success for the same key in another tenant.
|
|
65
|
+
- Tenant isolation: rows created for `tenant-a` are invisible to `list('tenant-b')`. The provider hands the suite the non-bypass `coreloom_runtime` role, so this runs against real forced row-level security; also assert that a call without tenant context fails with `TENANT_CONTEXT_REQUIRED`.
|
|
66
|
+
- Identity: `moduleDefinition.manifest.id` equals the module id (keeps `module.json` and `src/index.ts` aligned). The scaffold writes this and the isolation case; everything else in this list is yours.
|
|
67
|
+
|
|
68
|
+
Assert at the observation boundary: status code, `error.code`, returned record fields. Do not assert internal helper names, call order, or SQL text.
|
|
69
|
+
|
|
70
|
+
## 5. Break the implementation
|
|
71
|
+
|
|
72
|
+
A regression test that never failed proves nothing. For each new test: comment out the guard it protects (`if (denial) return denial;`, the `WHERE tenant_id = $1`, the `UNIQUE` constraint), run the suite, confirm the test fails, restore the code. Record in the handoff which tests were verified this way.
|
|
73
|
+
|
|
74
|
+
## 6. Flake sources here
|
|
75
|
+
|
|
76
|
+
`Date.now()` in `createdAt` (sort by `sku`, not by time); `randomUUID()` ids (never assert them); scrypt with the default cost is slow, so tests pass `passwordHash: { cost: 2 ** 12, ... }` as `modules/auth/tests/endpoints.test.ts` does; two tests sharing one provider see each other's rows unless the tables are truncated between them, so take a fresh fixture per test from the file's `tests/support/database.ts` helper.
|
|
77
|
+
|
|
78
|
+
## 7. Landing
|
|
79
|
+
|
|
80
|
+
Sandbox: the `tests` gate output appears in the chat after your turn. Repository root: `pnpm --filter @flowdular/module-<dir> test`, then `pnpm verify` before a PR.
|
|
81
|
+
|
|
82
|
+
## Pitfalls
|
|
83
|
+
|
|
84
|
+
- `expect(() => service.create(...)).toThrowError(/active tenant/)` pins a message; prefer the error `code` (`DUPLICATE_SKU`) when the class exposes one.
|
|
85
|
+
- A test that imports `@flowdular/sdk/ui` pulls fonts and CSS; keep client tests to `.ts` helpers.
|
|
86
|
+
- `vitest run` picks up `tests/**/*.test.ts`; a `.spec.ts` name also works but keep one convention.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: translations-i18n
|
|
3
|
+
description: >-
|
|
4
|
+
Add or review Flowdular UI translations through the shared client runtime,
|
|
5
|
+
module bundles, locale-aware formatting, and validation gates.
|
|
6
|
+
roles:
|
|
7
|
+
- business-manager
|
|
8
|
+
- frontend-engineer
|
|
9
|
+
- ux-designer
|
|
10
|
+
- module-executor
|
|
11
|
+
when: A module adds user-facing copy, a locale changes, a raw translation key appears, or UI must support another language.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Translate Flowdular UI
|
|
15
|
+
|
|
16
|
+
Flowdular loads translations at runtime. The shell owns locale selection and the fallback chain; each module owns its copy.
|
|
17
|
+
|
|
18
|
+
## Runtime contract
|
|
19
|
+
|
|
20
|
+
- `packages/client/src/i18n` registers the shell bundle and every enabled module bundle. Resolution is active locale, then `en`, then the key itself so a missing key stays visible.
|
|
21
|
+
- A module contribution imports `translations/en.json` and every declared locale, then returns `translations: { en, pl }` with its `moduleId`.
|
|
22
|
+
- Use fully qualified keys with `t()`, for example `t('catalog.items.title')`. In `.tsrx`, import from `@flowdular/sdk/client`. In plain `.ts` helpers, import from `@flowdular/sdk/client/i18n` so tests do not pull the TSRX shell entry.
|
|
23
|
+
- Navigation and account-menu labels use getters. Contributions are created before their bundles are registered, so eager `label: t(...)` can paint a raw key.
|
|
24
|
+
- Locale-sensitive dates, numbers and currency use `activeLocale()` with `Intl.DateTimeFormat` or `Intl.NumberFormat`.
|
|
25
|
+
- The personal locale selector lives in Profile and applies immediately. The tenant default remains an Administration setting and is the fallback when the browser has no personal choice.
|
|
26
|
+
|
|
27
|
+
## Module workflow
|
|
28
|
+
|
|
29
|
+
1. Keep the same locale list in `spec/module.yaml`, `module.json` and `flowdular.json`. Every module ships `en`.
|
|
30
|
+
2. Put all user-facing labels, hints, empty states, errors and accessible names in `translations/<locale>.json`. Keep flat, module-local keys such as `items.form.save`; the runtime adds the module namespace.
|
|
31
|
+
3. Add the same key to every locale in the same change. Write natural copy in each language.
|
|
32
|
+
4. Import the bundles in `src/client/contribution.tsrx` and expose them through `translations`.
|
|
33
|
+
5. Replace literals with `t('<module>.<key>')`. Dynamic families such as `t('expenses.status.' + status)` require every possible suffix in every bundle.
|
|
34
|
+
6. For a new locale-sensitive helper, add a test that changes the active locale and proves both the text and formatting.
|
|
35
|
+
|
|
36
|
+
Navigation pattern:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { t, type ModuleClientContribution } from '@flowdular/sdk/client';
|
|
40
|
+
import translationsEn from '../../translations/en.json';
|
|
41
|
+
import translationsPl from '../../translations/pl.json';
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
moduleId: 'inventory.core',
|
|
45
|
+
translations: { en: translationsEn, pl: translationsPl },
|
|
46
|
+
navigation: [
|
|
47
|
+
{
|
|
48
|
+
get label() {
|
|
49
|
+
return t('inventory.navigation.label');
|
|
50
|
+
},
|
|
51
|
+
get description() {
|
|
52
|
+
return t('inventory.navigation.description');
|
|
53
|
+
},
|
|
54
|
+
// remaining contribution fields
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Validation
|
|
61
|
+
|
|
62
|
+
Run:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pnpm flowdular module validate --module <module-id>
|
|
66
|
+
pnpm --filter @flowdular/module-<dir> typecheck
|
|
67
|
+
pnpm --filter @flowdular/module-<dir> test
|
|
68
|
+
pnpm format:check
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`module validate` rejects a missing locale file, mismatched locale key sets, and a static `t('module.key')` whose module bundle does not contain the key. A dynamic key cannot be proven statically, so test its complete value set.
|
|
72
|
+
|
|
73
|
+
When a raw key appears in the UI, check in this order:
|
|
74
|
+
|
|
75
|
+
1. The key exists in `translations/en.json` and the active locale.
|
|
76
|
+
2. The contribution exposes the bundle under the correct `moduleId` namespace.
|
|
77
|
+
3. Navigation copy is lazy through getters.
|
|
78
|
+
4. The running dev server has rebuilt after the contribution changed.
|
|
79
|
+
|
|
80
|
+
## Do not
|
|
81
|
+
|
|
82
|
+
- Add a module-local translation runtime or import JSON directly in each view.
|
|
83
|
+
- Leave English fallbacks in client API helpers. Use a translated fallback and preserve server messages when present.
|
|
84
|
+
- Translate identifiers, provider names, currency codes, shortcuts or stable error codes.
|
|
85
|
+
- Hide a missing key with an empty string.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ux-design
|
|
3
|
+
description: >-
|
|
4
|
+
Design a module screen on the shared design system, with the record-screen
|
|
5
|
+
recipe, the five states, the component and class inventory, and the icon keys.
|
|
6
|
+
roles:
|
|
7
|
+
- ux-designer
|
|
8
|
+
- frontend-engineer
|
|
9
|
+
when: A screen, drawer form, dashboard widget, or copy is being designed or reviewed.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Design a screen
|
|
13
|
+
|
|
14
|
+
`docs/design-system.md` (in a session: `reference/design-system.md`) is the only source of visual decisions. Primitives live in `packages/ui` (`reference/packages/ui/components/*.tsrx` and `components.css`). The reference screen is `.ai/references/catalog/src/client/CatalogView.tsrx` with `CatalogItemForm.tsrx`.
|
|
15
|
+
|
|
16
|
+
## 1. Rules (design-system.md, section Rules)
|
|
17
|
+
|
|
18
|
+
1. Primitives first: a `ui-*` class or an exported component before any new visual code.
|
|
19
|
+
2. Colors, fonts, sizes, radii and shadows only from tokens (`var(--...)`); no hex in module CSS.
|
|
20
|
+
3. Never restyle or override a `ui-*` class outside `packages/ui`.
|
|
21
|
+
4. A missing primitive becomes a module-local component on tokens, flagged as a promotion candidate for `packages/ui`.
|
|
22
|
+
5. Blue is action and selection; green, amber and red are state; copper is the brand only.
|
|
23
|
+
6. Minimum text size 12 px; labels `--text-xs` uppercase; numbers tabular (`.num`, `ui-kpi__value`).
|
|
24
|
+
7. Containment is owned by the primitives: children of `ui-view`, `ui-two-col`, `ui-grid-2`, `ui-kpi-grid` shrink, long words wrap, wide content scrolls inside `ui-table-wrap`.
|
|
25
|
+
8. `Kpi` is a stat tile: the value is a number or a short state word; identifiers, addresses and paths go into `note` or a `ui-mono` line.
|
|
26
|
+
9. Records own the page; creating and editing happens in a `Drawer`. Never split the width between a table and a form.
|
|
27
|
+
|
|
28
|
+
## 2. Record screen recipe
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
div.ui-view
|
|
32
|
+
PageHeader eyebrow title description actions: Button sm [Icon refresh 14] Refresh, Button sm primary [Icon plus 14] New ...
|
|
33
|
+
Alert only when error && !formOpen
|
|
34
|
+
TableCard title count head is one line: title with its count left, SearchField and Filters right
|
|
35
|
+
search SearchField value placeholder label onInput
|
|
36
|
+
filters Filters open onToggle activeCount; the controls live inside the dropdown
|
|
37
|
+
columns rows rowKey columns is a module-level readonly TableColumn<Row>[] outside the component
|
|
38
|
+
status 'loading' | 'idle' 'loading' only while status === 'loading' && rows.length === 0
|
|
39
|
+
empty emptyFiltered filtered filtered picks which of the two the table renders
|
|
40
|
+
actions actionsLabel visible compact buttons; undefined when the scope is missing
|
|
41
|
+
note one constraint worth stating
|
|
42
|
+
Drawer open title subtitle onClose form keyed by 'form-' + formSession
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`TableCard` is the record card and `Table` is the only table in the product: never hand-roll `table.ui-table` again, and never rebuild the head, the loading row or the empty state that these already own. `actions(row)` returns `TableAction[]`; the component renders visible compact buttons in its narrow trailing column. Do not build a dropdown or module-owned action markup. Fixed column widths apply through loading, empty and populated states. A cell returns nodes: `span.ui-cell` (`<b>` primary, `<small>` secondary), `ui-mono` for an identifier, `Tag` for state, `numeric: true` on the column for tabular figures.
|
|
46
|
+
|
|
47
|
+
The shared `Table` is backed by the official `@octanejs/tanstack-table` adapter. A module never imports TanStack directly. It supplies the Flowdular columns, rows and actions above, while `@flowdular/sdk/ui` owns the features, row model, header model and cell rendering.
|
|
48
|
+
|
|
49
|
+
Every column declares `width`. Primary identity and descriptions get the largest share, dates and identifiers a medium share, and counts or status the smallest. For a table with actions, data widths normally add up to about 90 percent because the shared action column is 160 px. Without actions they add up to 100 percent. Do not leave all columns unspecified: equal distribution wastes space and weakens the hierarchy.
|
|
50
|
+
|
|
51
|
+
Drawer form: `form.ui-drawer__form > div.ui-drawer__body > div.ui-form > div.ui-form__row > FormField label required help` wrapping a native `input.ui-input`, `select.ui-select` or `textarea.ui-textarea`; `Alert` inside the body for the submit error; `div.ui-drawer__foot` with `<small>` for the constraint and `div.ui-form__actions` (Cancel, primary submit with `disabled={busy}` and a progressive label `Creating…`). `Drawer width="lg"` when rows have two columns or an editor.
|
|
52
|
+
|
|
53
|
+
Read-only master-detail (runs, playground) keeps `ui-two-col` (+ `--wide-aside`). Admin overviews use `ui-kpi-grid` with several `Kpi`; a module dashboard widget is one `Kpi` with `href` and `linkLabel`, rendered by the shell in `dashboard.metrics`.
|
|
54
|
+
|
|
55
|
+
## 3. Five states
|
|
56
|
+
|
|
57
|
+
- Loading: `Table status="loading"` while `status === 'loading' && rows.length === 0`, so a refresh never blanks rows the user is reading.
|
|
58
|
+
- Empty: `empty` with an icon and a sentence that names the first action; `emptyFiltered` says no match and is chosen by `filtered`.
|
|
59
|
+
- Error: `Alert` (tone `danger` default) under the header, or inside the drawer while the form is open.
|
|
60
|
+
- Populated: the `Table` rows, or a list where records are not tabular.
|
|
61
|
+
- Denied: the shell already hides navigation and widgets whose `scope` the principal lacks. Inside a view, pass booleans derived from `ModuleClientContext.scopes` (`canManage={options.scopes.includes(X_PERMISSIONS.manage)}` in `contribution.tsrx`, as `modules/agents` does) and do not render the action. A 403 from the server still becomes an `Alert`; it is never a crash.
|
|
62
|
+
|
|
63
|
+
## 4. Component and prop inventory (`packages/ui/src/components`)
|
|
64
|
+
|
|
65
|
+
- `Button`: `variant` primary, secondary (default), ghost, danger; `size` sm, md, lg; `type` button, submit; `block`; `disabled`; `onClick`.
|
|
66
|
+
- `FormField`: `label`, `required`, `help`, `error`; one control child with `ui-input`, `ui-select` or `ui-textarea`.
|
|
67
|
+
- `SearchField`: `value`, `placeholder`, `label` (accessible name), `onInput(value)`.
|
|
68
|
+
- `Table`: `columns: TableColumn<Row>[]` (`key`, `header`, required `width`, `cell(row)`, `numeric`), `rows`, `rowKey(row)`, `status`, `loadingLabel`, `empty`, `emptyFiltered`, `filtered`, `actions(row): TableAction[]`, `actionsLabel`, optional stable `actionsWidth` (160 px default, 280 px for two actions), `onSelect(row)`, `selectedKey`, `caption`.
|
|
69
|
+
- `TableCard`: every `Table` prop plus `title`, `count`, `head`, `search`, `filters`, `before`, `after`, `note`, `noteIcon`.
|
|
70
|
+
- `Filters`: `open`, `onToggle`, `activeCount`, `label`; children are the filter controls, which belong in the dropdown and nowhere else.
|
|
71
|
+
- `CheckGrid`: `groups: { label, options: { value, label, hint? }[] }[]`, `value: string[]`, `mono`, `disabled`, `onChange(next)`.
|
|
72
|
+
- `Drawer`: `open`, `title`, `subtitle`, `width` md or lg, `onClose`; child is `ui-drawer__form` or `ui-drawer__body`. Escape and the scrim close it.
|
|
73
|
+
- `Tag`: `tone` neutral, success, warning, danger, info, ink; `dot`; `mono`.
|
|
74
|
+
- `Kpi`: `label`, `value` (string), `unit`, `badge`, `note`, `href`, `linkLabel`.
|
|
75
|
+
- `PageHeader`: `eyebrow`, `title`, `description`; children are the right-side actions.
|
|
76
|
+
- `EmptyState`: `icon`, `title`, `code`, children as the sentence.
|
|
77
|
+
- `Alert`: `tone` danger (default), warning, info.
|
|
78
|
+
- `Avatar`: `name`, `square` (organizations), `large`.
|
|
79
|
+
- `Icon`: `name`, `size` (18 default, 16 in controls, 14 in `Button size="sm"`), `strokeWidth`.
|
|
80
|
+
- `BrandMark`: `size`, `signature`, `tone`; brand moments only.
|
|
81
|
+
|
|
82
|
+
Icon keys (`ICON_PATHS`, `packages/ui/src/icons/Icon.tsrx`): `dashboard`, `parties`, `catalog`, `user`, `users`, `shield`, `code`, `modules`, `file-text`, `play`, `bot`, `flask`, `activity`, `plug`, `search`, `chevron-down`, `chevrons-up-down`, `plus`, `panel-left`, `check`, `filter`, `download`, `more`, `external`, `alert`, `x`, `sign-out`, `refresh`, `help`, `key`, `settings`, `braces`. An unknown name renders `modules` silently, so check the list.
|
|
83
|
+
|
|
84
|
+
## 5. Classes a module writes by hand (`packages/ui/src/styles/components.css`)
|
|
85
|
+
|
|
86
|
+
Layout `ui-view`, `ui-two-col` (+`--wide-aside`), `ui-grid-2`, `ui-kpi-grid`, `ui-tag-cloud`, `ui-section-head` (h2 plus actions inside a view), `ui-toolbar` (+`__spacer`). Surfaces `ui-card` (+`__head`, `__title`, `__body`). Data `ui-table` (+`ui-table-wrap`, `ui-table__empty`, `ui-table__state` for a dot plus label, `.num`), `ui-cell` (+`ui-cell__muted`), `ui-mono`, `ui-code`, `ui-dot` (+`--muted`). Row action classes are component-owned and are never written by a module. Forms `ui-form` (+`__row`, `__row--4`, `__foot`, `__actions`), `ui-input` (+`--error`), `ui-select`, `ui-textarea` (+`--error`), `ui-checkbox`, `ui-label`, `ui-help` (+`--error`). Drawer `ui-drawer__form`, `ui-drawer__body`, `ui-drawer__foot`. Bits `ui-kbd`, `ui-note`, `ui-menu` (+`__label`, `__item`, `__item--active`, `__item--danger`, `__sep`), `ui-btn ui-btn--icon` for an icon-only button. Classes rendered by components (`ui-drawer__panel`, `ui-search`, `ui-page-head*`, `ui-field`, `ui-empty*`, `ui-alert*`, `ui-tag*`, `ui-kpi__*`, `ui-checks*`, `ui-avatar*`) are not written by hand.
|
|
87
|
+
|
|
88
|
+
## 6. Copy
|
|
89
|
+
|
|
90
|
+
User-facing copy lives in every declared `translations/*.json` bundle and is read with fully qualified `t()` keys. Eyebrow names the domain, title names the records, and description is one sentence. Table headers say what the value is. Buttons start with a verb. Loading text ends with `…`. Drawer footer states the constraint the user cannot see. Write natural copy in each locale, with no exclamation marks or database jargon.
|
|
91
|
+
|
|
92
|
+
## Pitfalls
|
|
93
|
+
|
|
94
|
+
- `Kpi value={items.length}` does not typecheck; use `String(items.length)`.
|
|
95
|
+
- A `Tag` for a lifecycle state uses `success` for active and `neutral` for archived, with `dot`.
|
|
96
|
+
- An `Icon` inside `Button size="sm"` is 14, not 18.
|
|
97
|
+
- A new component file per screen, form, table, or stateful region; a page composes them.
|