@weft-js/cli 0.1.1 → 0.1.3
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 +534 -54
- package/bin/weft.mjs +8 -1
- package/docs/async.md +16 -6
- package/docs/browser.md +22 -8
- package/docs/config.md +39 -9
- package/docs/core.md +30 -0
- package/docs/deploy.md +33 -8
- package/docs/design.md +12 -3
- package/docs/entrypoints.md +79 -0
- package/docs/fonts.md +12 -2
- package/docs/icons.md +13 -7
- package/docs/lists.md +3 -1
- package/docs/meta.md +13 -3
- package/docs/plugins.md +32 -9
- package/docs/props.md +13 -2
- package/docs/reactivity.md +26 -0
- package/docs/rules.md +19 -12
- package/docs/rust.md +47 -17
- package/docs/start-short.md +3 -2
- package/docs/start.md +226 -52
- package/docs/state.md +33 -7
- package/docs/style.md +31 -8
- package/docs/surface.md +61 -15
- package/docs/testing.md +15 -5
- package/docs/theme.md +182 -0
- package/docs/ui-state.md +19 -6
- package/docs/verify.md +13 -0
- package/package.json +13 -4
- package/src/add/config.mjs +93 -0
- package/src/add/errors.mjs +2 -0
- package/src/add/setup.mjs +88 -0
- package/src/add-rust.mjs +82 -27
- package/src/add.mjs +209 -181
- package/src/agents-md.mjs +144 -43
- package/src/alias.mjs +16 -5
- package/src/audit/page.mjs +313 -0
- package/src/audit/report.mjs +69 -0
- package/src/audit/utils.mjs +5 -0
- package/src/audit.mjs +159 -206
- package/src/browser/cli.mjs +24 -5
- package/src/browser/client.mjs +189 -25
- package/src/browser/commands.mjs +166 -51
- package/src/catalog/audit.mjs +129 -0
- package/src/catalog/auth.mjs +40 -0
- package/src/catalog/compiler.mjs +337 -0
- package/src/catalog/contracts.mjs +18 -0
- package/src/catalog/database.mjs +52 -0
- package/src/catalog/deploy.mjs +117 -0
- package/src/catalog/design.mjs +159 -0
- package/src/catalog/forms.mjs +25 -0
- package/src/catalog/hydration.mjs +29 -0
- package/src/catalog/icons.mjs +24 -0
- package/src/catalog/media.mjs +163 -0
- package/src/catalog/metadata.mjs +116 -0
- package/src/catalog/plugins.mjs +228 -0
- package/src/catalog/realtime.mjs +195 -0
- package/src/catalog/rust.mjs +75 -0
- package/src/catalog/server-functions.mjs +92 -0
- package/src/catalog/state.mjs +39 -0
- package/src/catalog/style-groups.mjs +54 -0
- package/src/catalog/tests.mjs +127 -0
- package/src/catalog/text.mjs +200 -0
- package/src/catalog/ui-state.mjs +42 -0
- package/src/catalog.mjs +67 -460
- package/src/check/runtime.mjs +280 -0
- package/src/check.mjs +198 -208
- package/src/cli/authoring.mjs +213 -0
- package/src/cli/project.mjs +302 -0
- package/src/cli.mjs +222 -250
- package/src/config.mjs +1 -1
- package/src/contributions.mjs +75 -25
- package/src/db-layer.mjs +13 -5
- package/src/deploy/cdn.mjs +94 -16
- package/src/deploy/files.mjs +100 -21
- package/src/deploy/fit.mjs +127 -23
- package/src/deploy/hosts.mjs +90 -20
- package/src/deploy/index/cli.mjs +20 -0
- package/src/deploy/index/environment.mjs +69 -0
- package/src/deploy/index/steps.mjs +87 -0
- package/src/deploy/index.mjs +228 -114
- package/src/deploy/needs.mjs +88 -31
- package/src/design-checks/accessible-names.mjs +62 -0
- package/src/design-checks/contrast.mjs +43 -0
- package/src/design-checks/csp.mjs +60 -0
- package/src/design-checks/files.mjs +59 -0
- package/src/design-checks/heading-links.mjs +61 -0
- package/src/design-checks/interaction.mjs +65 -0
- package/src/design-checks/jsx.mjs +67 -0
- package/src/design-checks/layout-values.mjs +68 -0
- package/src/design-checks/native-controls.mjs +48 -0
- package/src/design-checks/outline.mjs +39 -0
- package/src/design-checks/reporter.mjs +37 -0
- package/src/design-checks/rules.mjs +274 -0
- package/src/design-checks/values.mjs +262 -0
- package/src/design-checks.mjs +10 -400
- package/src/diag.mjs +32 -8
- package/src/docs.mjs +138 -43
- package/src/doctor.mjs +20 -8
- package/src/files.mjs +18 -5
- package/src/fix.mjs +10 -2
- package/src/gate/after.mjs +40 -0
- package/src/gate/before.mjs +54 -0
- package/src/gate/classify.mjs +72 -0
- package/src/gate/mutation.mjs +209 -0
- package/src/gate/resolve-cases.mjs +22 -0
- package/src/gate/result.mjs +63 -0
- package/src/gate/run.mjs +75 -0
- package/src/gate.mjs +104 -202
- package/src/infer/box-props.cjs +140 -0
- package/src/infer/core-boxes.cjs +46 -0
- package/src/infer/derived-writes.cjs +95 -0
- package/src/infer/engine/checks.cjs +193 -0
- package/src/infer/engine/flow.cjs +293 -0
- package/src/infer/engine/report.cjs +158 -0
- package/src/infer/engine/utils.cjs +15 -0
- package/src/infer/engine.cjs +108 -368
- package/src/infer/facts/sites.cjs +114 -0
- package/src/infer/facts.cjs +210 -117
- package/src/infer/hosts.cjs +69 -22
- package/src/infer/lsp.mjs +149 -40
- package/src/infer/session.cjs +73 -17
- package/src/infer/shadow.cjs +13 -5
- package/src/infer/worker.cjs +57 -9
- package/src/jsx-scan.mjs +63 -21
- package/src/load-config/schema.mjs +281 -0
- package/src/load-config.mjs +137 -138
- package/src/map.mjs +93 -26
- package/src/mcp/browser-tools.mjs +155 -0
- package/src/mcp.mjs +147 -69
- package/src/meta-table.mjs +43 -8
- package/src/mutate.mjs +120 -36
- package/src/plugin-checks.mjs +52 -23
- package/src/plugins.mjs +125 -39
- package/src/project.mjs +170 -79
- package/src/props-checks.mjs +24 -6
- package/src/props-listener.mjs +27 -9
- package/src/react-checks.mjs +35 -4
- package/src/server-state-checks.mjs +34 -0
- package/src/share-checks.mjs +121 -37
- package/src/site.mjs +33 -6
- package/src/slots.mjs +104 -19
- package/src/start/build.mjs +116 -0
- package/src/start/routes.mjs +38 -0
- package/src/start.mjs +193 -95
- package/src/state-checks/core-state.mjs +223 -0
- package/src/state-checks/modules.mjs +17 -0
- package/src/state-checks/scan.mjs +232 -0
- package/src/state-checks/server-graph.mjs +65 -0
- package/src/state-checks.mjs +109 -133
- package/src/style/find.mjs +121 -0
- package/src/style.mjs +133 -127
- package/src/surface-checks.mjs +212 -46
- package/src/tailwind.mjs +5 -4
- package/src/tasks.mjs +189 -27
- package/src/test-rules/context.mjs +109 -0
- package/src/test-rules/file-rules.mjs +279 -0
- package/src/test-rules/rule-helpers.mjs +244 -0
- package/src/test-rules/syntax.mjs +223 -0
- package/src/test-rules/test-rules.mjs +127 -0
- package/src/testdiff.mjs +94 -32
- package/src/tests.mjs +31 -483
- package/src/text-checks.mjs +229 -69
- package/src/tokens.mjs +12 -4
- package/src/trust/scan.mjs +95 -0
- package/src/trust.mjs +214 -102
- package/src/weft.mjs +41 -9
- package/src/workspace.mjs +96 -19
- package/ts-plugin/context.js +57 -0
- package/ts-plugin/index.js +4 -124
- package/ts-plugin/mapping.js +65 -0
- package/ts-plugin/proxy.js +144 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/** tests diagnostic entries in catalog order. */
|
|
2
|
+
export const tests = [
|
|
3
|
+
{
|
|
4
|
+
code: 'T001',
|
|
5
|
+
severity: 'error',
|
|
6
|
+
by: 'cli',
|
|
7
|
+
catches:
|
|
8
|
+
'A test with no assertion (or an `expect(x)` with no matcher): it passes as long as nothing throws. `@weft-js/core/test` also fails such a test at runtime',
|
|
9
|
+
fix: 'Assert what the user sees: `expect(screen.getByRole(…)).toHaveTextContent(…)`',
|
|
10
|
+
example:
|
|
11
|
+
"import { render } from '@weft-js/core/test'\nimport { Cart } from './Cart'\ntest('renders', () => { render(Cart) })",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
code: 'T002',
|
|
15
|
+
severity: 'error',
|
|
16
|
+
by: 'cli',
|
|
17
|
+
catches: 'Tautology: `expect` on a literal, or a value compared with itself',
|
|
18
|
+
fix: 'Assert on the output of the code under test',
|
|
19
|
+
example: "test('works', () => { expect(true).toBe(true) })",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
code: 'T003',
|
|
23
|
+
severity: 'error',
|
|
24
|
+
by: 'cli',
|
|
25
|
+
catches:
|
|
26
|
+
'Asserting a value the test just set (`n.value = 5; expect(n.value).toBe(5)`) without going through the UI or the graph',
|
|
27
|
+
fix: 'Act through the UI (`await user.click(…)`) or assert on what depends on the value',
|
|
28
|
+
example:
|
|
29
|
+
"import { state } from '@weft-js/core'\ntest('count', () => { const n = state(0); n.value = 5; expect(n.value).toBe(5) })",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: 'T004',
|
|
33
|
+
severity: 'error',
|
|
34
|
+
by: 'cli',
|
|
35
|
+
catches: 'Sleeps, real timers or `waitFor` polling in a test',
|
|
36
|
+
fix: '`await flush()`; for time, `clock.install()` and `await clock.advance(ms)` from @weft-js/core/test',
|
|
37
|
+
example:
|
|
38
|
+
"test('later', async () => { await new Promise(r => setTimeout(r, 100)); expect(1 + 1).toBe(2) })",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
code: 'T005',
|
|
42
|
+
severity: 'warning',
|
|
43
|
+
by: 'cli',
|
|
44
|
+
catches:
|
|
45
|
+
'Implementation query: `container`, CSS selectors, classes, `innerHTML`, test ids, or app state read directly in a component test',
|
|
46
|
+
fix: 'Query by role, label or text (`screen.getByRole`, `getByLabelText`, `getByText`)',
|
|
47
|
+
example:
|
|
48
|
+
"import { render } from '@weft-js/core/test'\nimport { Cart } from './Cart'\ntest('total', () => { const { container } = render(Cart); expect(container.querySelector('.total')!.textContent).toBe('$0') })",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
code: 'T006',
|
|
52
|
+
severity: 'error',
|
|
53
|
+
by: 'cli',
|
|
54
|
+
catches:
|
|
55
|
+
"Mocking weft (`vi.mock('@weft-js/core')`) or the unit under test, so the test checks the mock",
|
|
56
|
+
fix: 'Fake the edges (network, storage) and render the real component',
|
|
57
|
+
example:
|
|
58
|
+
"import { render } from '@weft-js/core/test'\nimport { Cart } from './Cart'\nvi.mock('./Cart')\ntest('cart', () => { render(Cart); expect(document.body.textContent).toBe('') })",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
code: 'T007',
|
|
62
|
+
severity: 'error',
|
|
63
|
+
by: 'cli',
|
|
64
|
+
catches: "Snapshot without approval: it records today's output and passes against it",
|
|
65
|
+
fix: 'Assert the behavior with role/label/text queries, or approve it with `// weft-allow T007: <reason>`',
|
|
66
|
+
example:
|
|
67
|
+
"import { render } from '@weft-js/core/test'\nimport { Cart } from './Cart'\ntest('cart', () => { render(Cart); expect(document.body.innerHTML).toMatchSnapshot() })",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
code: 'T008',
|
|
71
|
+
severity: 'error',
|
|
72
|
+
by: 'cli',
|
|
73
|
+
catches:
|
|
74
|
+
'Conditional `expect` (`if`, `?:`, `&&`, `catch`), or a test whose only `expect`s are in a loop',
|
|
75
|
+
fix: 'Make the condition its own assertion, then assert unconditionally; `expect(fn).toThrow()` for errors',
|
|
76
|
+
example: "test('maybe', () => { const xs = [1, 2]; if (xs.length > 5) expect(xs[0]).toBe(1) })",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
code: 'T009',
|
|
80
|
+
severity: 'error',
|
|
81
|
+
by: 'cli',
|
|
82
|
+
catches: '`.only` or `.skip` committed (also `fit`, `xit`, `xdescribe`)',
|
|
83
|
+
fix: 'Remove `.only`; fix or delete a skipped test (`test.fails` for one meant to fail)',
|
|
84
|
+
example: "test.only('focused', () => { expect(Math.max(1, 2)).toBe(2) })",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
code: 'T010',
|
|
88
|
+
severity: 'error',
|
|
89
|
+
by: 'gate',
|
|
90
|
+
catches:
|
|
91
|
+
'A new or changed test passes on the code before the change (fail-before), so it does not test the change',
|
|
92
|
+
fix: 'Assert what the change makes different; a test for existing behavior goes in its own change with no source edits',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
code: 'T011',
|
|
96
|
+
severity: 'error',
|
|
97
|
+
by: 'gate',
|
|
98
|
+
catches:
|
|
99
|
+
'A new test kills none of the mutants of the changed lines: it would still pass if that code were wrong (a warning when the mutants were sampled)',
|
|
100
|
+
fix: "Assert what the changed code computes; the gate's surviving mutants say what to check",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
code: 'T012',
|
|
104
|
+
severity: 'error',
|
|
105
|
+
by: 'test',
|
|
106
|
+
catches:
|
|
107
|
+
'A test ends with @weft-js/core/browser listeners, intervals or subscribed values still open (checked by @weft-js/core/test after each test)',
|
|
108
|
+
fix: 'Unmount what the test created (dispose any `createRoot`), or fix the leak',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
code: 'T013',
|
|
112
|
+
severity: 'error',
|
|
113
|
+
by: 'cli',
|
|
114
|
+
catches: 'A real `WebSocket` or `EventSource` in a fast test',
|
|
115
|
+
fix: 'Use the fake transport: `net.push`, `net.poke`, `net.drop`',
|
|
116
|
+
example:
|
|
117
|
+
"test('feed', () => { const socket = new WebSocket('ws://localhost/feed'); expect(socket).toBeTruthy() })",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
code: 'T020',
|
|
121
|
+
severity: 'error',
|
|
122
|
+
by: 'gate',
|
|
123
|
+
catches:
|
|
124
|
+
'An existing test deleted, skipped, marked `.fails`, or with an assertion removed or loosened (`toBe(3)` → `toBeTruthy()`)',
|
|
125
|
+
fix: 'Restore it, or keep the change with `// weft-allow T020: <reason>` on that test',
|
|
126
|
+
},
|
|
127
|
+
]
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/** text diagnostic entries in catalog order. */
|
|
2
|
+
export const text = [
|
|
3
|
+
// Text, type and skeletons (research 11 §8.5; research 26 §7; research 31 §7). `cli`: weft check (text-checks.mjs).
|
|
4
|
+
{
|
|
5
|
+
code: 'X001',
|
|
6
|
+
severity: 'warning',
|
|
7
|
+
by: 'runtime',
|
|
8
|
+
catches:
|
|
9
|
+
'`s.text()` predicted one height and its DOM mirror renders another: `font`, `lineHeight` or `width` differs from the CSS',
|
|
10
|
+
fix: 'Drop `font` and `lineHeight` so they come from CSS, or make them match; the message names the values',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
code: 'X002',
|
|
14
|
+
severity: 'warning',
|
|
15
|
+
by: 'cli',
|
|
16
|
+
catches:
|
|
17
|
+
'`system-ui`, `-apple-system` or a generic-only family on a measured `s.text()`: canvas and CSS can resolve it differently (also checked in dev, for fonts that come from CSS)',
|
|
18
|
+
fix: 'Name a family',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
code: 'X003',
|
|
22
|
+
severity: 'warning',
|
|
23
|
+
by: 'runtime',
|
|
24
|
+
catches:
|
|
25
|
+
'`s.text()` prepared while its font isn\'t loaded and no `"<Family> Fallback"` face exists: it\'s measured on another font until the swap',
|
|
26
|
+
fix: 'Declare the font in `text({ fonts })`, which generates the fallback',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
code: 'X004',
|
|
30
|
+
severity: 'warning',
|
|
31
|
+
by: 'runtime',
|
|
32
|
+
catches:
|
|
33
|
+
"A surface text mirror uses a style line breaking can't predict: `text-wrap: balance|pretty`, `hyphens: auto`, `word-spacing`, `letter-spacing`, `font-feature-settings`, `font-variant-numeric`, `font-variation-settings`",
|
|
34
|
+
fix: 'Drop the style, or pass `exact: false`',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
code: 'X005',
|
|
38
|
+
severity: 'error',
|
|
39
|
+
by: 'cli',
|
|
40
|
+
catches:
|
|
41
|
+
"`font-size` in `vw`/`vi`/`cqi`/`cqw` with no `rem` term: zoom and the user's font size don't change it (WCAG 1.4.4)",
|
|
42
|
+
fix: '`clamp(rem, rem + cqi, rem)`; the suggestion keeps the same sizes at 375 and 1280 px',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
code: 'X006',
|
|
46
|
+
severity: 'error',
|
|
47
|
+
by: 'cli',
|
|
48
|
+
catches:
|
|
49
|
+
'A fluid `clamp()` font size whose max/min is over 2.5: 200% zoom no longer doubles it',
|
|
50
|
+
fix: 'Narrow the range (raise the minimum or lower the maximum)',
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
code: 'X008',
|
|
54
|
+
severity: 'error',
|
|
55
|
+
by: 'cli',
|
|
56
|
+
catches: 'Viewport meta with `user-scalable=no` or `maximum-scale` under 2: blocks pinch zoom',
|
|
57
|
+
fix: 'Remove them (autofix)',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
code: 'X009',
|
|
61
|
+
severity: 'error',
|
|
62
|
+
by: 'cli',
|
|
63
|
+
catches:
|
|
64
|
+
'`<Surface>` with neither `label` nor `decorative`: a screen reader finds an unnamed image',
|
|
65
|
+
fix: 'Add `label`, or `decorative` if it shows nothing a reader needs',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
code: 'X010',
|
|
69
|
+
severity: 'error',
|
|
70
|
+
by: 'cli',
|
|
71
|
+
catches:
|
|
72
|
+
'`fillText` or `strokeText` in a surface: text only the canvas knows, with no selection, find, translation or screen reader',
|
|
73
|
+
fix: 'Use `s.text()`, or wrap it in `s.decorative()` if it is truly decoration',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
code: 'X011',
|
|
77
|
+
severity: 'error',
|
|
78
|
+
by: 'cli',
|
|
79
|
+
catches:
|
|
80
|
+
'A surface text mirror hidden from assistive tech: `aria-hidden` on `<Surface>` or `s.el`, or `display: none`/`visibility: hidden` on `[data-surface-text]` (also checked in dev)',
|
|
81
|
+
fix: 'Leave the mirror to weft; use `decorative` for a surface with nothing to read',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
code: 'X012',
|
|
85
|
+
severity: 'error',
|
|
86
|
+
by: 'cli',
|
|
87
|
+
catches:
|
|
88
|
+
"CSS that defeats a surface's text fallback: `forced-color-adjust: none`, or a print rule hiding `[data-surface-text]`",
|
|
89
|
+
fix: 'Remove it: the mirror is what forced-colours mode and print show',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
code: 'X020',
|
|
93
|
+
severity: 'warning',
|
|
94
|
+
by: 'cli',
|
|
95
|
+
catches:
|
|
96
|
+
'A hand-written `@font-face` for a local file with no `"<Family> Fallback"` face: the font swap shifts layout',
|
|
97
|
+
fix: 'Declare the font in `text({ fonts })` (@weft-js/text), which generates a metric-matched fallback',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
code: 'X021',
|
|
101
|
+
severity: 'error',
|
|
102
|
+
by: 'cli',
|
|
103
|
+
catches: '`font-display: block` or `auto`: text is invisible while the font loads',
|
|
104
|
+
fix: '`swap` (autofix), or `optional` for faces that may skip',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
code: 'X022',
|
|
108
|
+
severity: 'warning',
|
|
109
|
+
by: 'cli',
|
|
110
|
+
catches:
|
|
111
|
+
"A local `@font-face` without `unicode-range` (not subset), or the font build's preloads over the 100 KB budget",
|
|
112
|
+
fix: '`text({ fonts })` subsets every face; preload fewer faces',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
code: 'X023',
|
|
116
|
+
severity: 'warning',
|
|
117
|
+
by: 'cli',
|
|
118
|
+
catches: 'A `<link rel="preload" as="font">` whose file no `@font-face` uses',
|
|
119
|
+
fix: 'Remove the preload',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
code: 'X030',
|
|
123
|
+
severity: 'error',
|
|
124
|
+
by: 'cli',
|
|
125
|
+
catches:
|
|
126
|
+
'`<img>`, `<video>` or `<iframe>` without `width`/`height` or `aspect-ratio` inside a `<Boundary>`: it lands at 0 px and pushes content down',
|
|
127
|
+
fix: '`<Media src={x}>` with a `?media` import (it always has dimensions), or set `width` and `height`',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
code: 'X031',
|
|
131
|
+
severity: 'warning',
|
|
132
|
+
by: 'runtime',
|
|
133
|
+
catches:
|
|
134
|
+
"A `pending` fallback whose box differs from the content's by more than 1 px at the swap (measured by `<Boundary>` in dev)",
|
|
135
|
+
fix: "Size the fallback like the content: warp's `<Shape>` with the content's counts",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
code: 'X032',
|
|
139
|
+
severity: 'warning',
|
|
140
|
+
by: 'runtime',
|
|
141
|
+
catches:
|
|
142
|
+
'A skeleton repeats a list with no count hint: no `limit` input, no `.max()` on the array, nothing seen yet. It shows 5 rows, so the swap can shift',
|
|
143
|
+
fix: 'Add a `limit` input or `.max()` to the array in `output`',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
code: 'X033',
|
|
147
|
+
severity: 'warning',
|
|
148
|
+
by: 'cli',
|
|
149
|
+
catches:
|
|
150
|
+
'`x.pending` renders a skeleton or spinner with no check that `x` has no value: every refetch flashes it',
|
|
151
|
+
fix: 'Let `<Boundary>` show first loads, and test `x.value === undefined` here. A refetch keeps the old value in place',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
code: 'X034',
|
|
155
|
+
severity: 'error',
|
|
156
|
+
by: 'cli',
|
|
157
|
+
catches:
|
|
158
|
+
'A `pending` fallback with its own `setTimeout`/`setInterval`: `<Boundary>` already owns the 150 ms / 400 ms timing',
|
|
159
|
+
fix: 'Remove the timers from the fallback',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
code: 'X040',
|
|
163
|
+
severity: 'warning',
|
|
164
|
+
by: 'cli',
|
|
165
|
+
catches: 'Per-grapheme DOM spans over a string known to exceed 200 graphemes',
|
|
166
|
+
fix: 'Use word-level motion or a Surface',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
code: 'X041',
|
|
170
|
+
severity: 'warning',
|
|
171
|
+
by: 'cli',
|
|
172
|
+
catches:
|
|
173
|
+
'Per-character spans without `aria-hidden`, or a parent with no accessible name: a screen reader spells the word out',
|
|
174
|
+
fix: '`aria-hidden` on each span and `aria-label` with the whole text on the parent',
|
|
175
|
+
},
|
|
176
|
+
// Media (research 15 §7.5, static rules in research 26 §2.5): reported by @weft-js/plugin-media's check hook.
|
|
177
|
+
{
|
|
178
|
+
code: 'X035',
|
|
179
|
+
severity: 'warning',
|
|
180
|
+
by: 'cli',
|
|
181
|
+
catches:
|
|
182
|
+
'Streamed text written straight into a scroll container: a reflowing tail moves the lines above it',
|
|
183
|
+
fix: 'Render it with `<Text stream>` from @weft-js/core/text: the height only grows',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
code: 'X036',
|
|
187
|
+
severity: 'warning',
|
|
188
|
+
by: 'runtime',
|
|
189
|
+
catches: 'A skeleton shows a string from `output` with no length hint, so its bar is a guess',
|
|
190
|
+
fix: "Add `.max()`, `.length()` or `.meta({ example })` to the schema, or an example in the query's `shape`",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
code: 'X037',
|
|
194
|
+
severity: 'warning',
|
|
195
|
+
by: 'cli',
|
|
196
|
+
catches:
|
|
197
|
+
'A `resource` or query made in the component that renders the `<Boundary>` meant to wait for it, and read inside it: the Boundary only waits for what its children make, so no fallback shows',
|
|
198
|
+
fix: 'Make it in a small component rendered inside the Boundary',
|
|
199
|
+
},
|
|
200
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** uiState diagnostic entries in catalog order. */
|
|
2
|
+
export const uiState = [
|
|
3
|
+
{
|
|
4
|
+
code: 'U001',
|
|
5
|
+
severity: 'error',
|
|
6
|
+
by: 'cli',
|
|
7
|
+
catches:
|
|
8
|
+
'A `share` key built from `Math.random`, `Date.now` or `crypto.randomUUID`: no other tab or client will ever have the same one',
|
|
9
|
+
fix: `Build it from a stable id: \`share: () => \`todo:\${props.id}\`\``,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
code: 'U002',
|
|
13
|
+
severity: 'warning',
|
|
14
|
+
by: 'cli',
|
|
15
|
+
catches:
|
|
16
|
+
'A `share` key taken from the URL or something typed: `realtime({ share })` then authorises a string the user chose',
|
|
17
|
+
fix: 'Build it from data the server knows, and have the rule check it',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
code: 'U003',
|
|
21
|
+
severity: 'warning',
|
|
22
|
+
by: 'cli',
|
|
23
|
+
catches:
|
|
24
|
+
'`interaction({ share })` in an app with no `realtime({ share })`: tabs work, and other clients are refused (R011)',
|
|
25
|
+
fix: 'Add `share: ({ ctx, key }) => …` to `realtime()` in `src/realtime.server.ts`, or leave it if tabs are all you want',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
code: 'U004',
|
|
29
|
+
severity: 'error',
|
|
30
|
+
by: 'cli',
|
|
31
|
+
catches: 'Writing `row.remote`: it is what others are doing, and only the network writes it',
|
|
32
|
+
fix: 'Write `row.hover`, or keep your own `state`',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: 'U005',
|
|
36
|
+
severity: 'error',
|
|
37
|
+
by: 'cli',
|
|
38
|
+
catches:
|
|
39
|
+
'`ui` on a warp component that does not take it: an attribute is written and nothing is drawn',
|
|
40
|
+
fix: 'Use Button, Checkbox, Switch, Radio, Toggle, Chip or Card, or read the row yourself',
|
|
41
|
+
},
|
|
42
|
+
]
|