@fugood/bricks-ctor 2.25.0-beta.5 → 2.25.0-beta.50
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/compile/__tests__/config-diff.test.js +100 -0
- package/compile/__tests__/index.test.js +365 -0
- package/compile/__tests__/util.test.js +317 -0
- package/compile/action-name-map.ts +64 -0
- package/compile/config-diff.ts +155 -0
- package/compile/index.ts +273 -32
- package/compile/util.ts +26 -7
- package/package.json +7 -3
- package/skills/bricks-ctor/SKILL.md +23 -17
- package/skills/bricks-ctor/{rules → references}/animation.md +3 -2
- package/skills/bricks-ctor/{rules → references}/architecture-patterns.md +18 -0
- package/skills/bricks-ctor/{rules → references}/automations.md +11 -0
- package/skills/bricks-ctor/references/buttress.md +245 -0
- package/skills/bricks-ctor/references/data-calculation.md +239 -0
- package/skills/bricks-ctor/references/simulator.md +132 -0
- package/skills/bricks-ctor/references/source-editing-tools.md +81 -0
- package/skills/bricks-ctor/references/verification-toolchain.md +200 -0
- package/skills/bricks-design/SKILL.md +150 -45
- package/skills/bricks-design/references/architecture-truths.md +132 -0
- package/skills/bricks-design/references/avoiding-complexity.md +91 -0
- package/skills/bricks-design/references/design-critique.md +195 -0
- package/skills/bricks-design/references/design-languages.md +265 -0
- package/skills/bricks-design/references/performance.md +116 -0
- package/skills/bricks-design/references/presentation-and-slideshow.md +137 -0
- package/skills/bricks-design/references/translating-inputs.md +152 -0
- package/skills/bricks-design/references/variations-and-tweaks.md +124 -0
- package/skills/bricks-design/references/when-the-brief-is-branded.md +284 -0
- package/skills/bricks-design/references/when-the-brief-is-vague.md +85 -0
- package/skills/bricks-design/references/workflow.md +134 -0
- package/skills/bricks-ux/SKILL.md +114 -0
- package/skills/bricks-ux/references/accessibility.md +162 -0
- package/skills/bricks-ux/references/flow-states.md +175 -0
- package/skills/bricks-ux/references/interaction-archetypes.md +189 -0
- package/skills/bricks-ux/references/monitoring-screens.md +153 -0
- package/skills/bricks-ux/references/pressable-composition.md +126 -0
- package/skills/bricks-ux/references/user-journey.md +168 -0
- package/skills/bricks-ux/references/ux-critique.md +256 -0
- package/tools/__tests__/_cli-error.test.ts +35 -0
- package/tools/_cli-error.ts +17 -0
- package/tools/_edits-log.ts +41 -0
- package/tools/_git-author.ts +10 -2
- package/tools/_last-pushed-commit.ts +28 -0
- package/tools/_shell.ts +8 -1
- package/tools/deploy.ts +17 -6
- package/tools/mcp-env.ts +13 -0
- package/tools/mcp-server.ts +8 -0
- package/tools/mcp-tools/__tests__/data-calc-editing.test.js +516 -0
- package/tools/mcp-tools/__tests__/entry-editing.test.js +866 -0
- package/tools/mcp-tools/__tests__/huggingface.test.ts +49 -0
- package/tools/mcp-tools/__tests__/icons.test.ts +21 -0
- package/tools/mcp-tools/__tests__/mcp-env.test.js +19 -0
- package/tools/mcp-tools/_editing-helpers.ts +58 -0
- package/tools/mcp-tools/_verify.ts +50 -0
- package/tools/mcp-tools/compile.ts +21 -9
- package/tools/mcp-tools/data-calc-editing.ts +1349 -0
- package/tools/mcp-tools/entry-editing.ts +2336 -0
- package/tools/mcp-tools/huggingface.ts +23 -13
- package/tools/mcp-tools/icons.ts +23 -7
- package/tools/mcp-tools/media.ts +4 -1
- package/tools/postinstall.ts +80 -3
- package/tools/pull.ts +93 -22
- package/tools/push-config.ts +114 -0
- package/tools/{preview-main.mjs → simulator-main.mjs} +207 -12
- package/tools/simulator-preload.cjs +16 -0
- package/tools/{preview.ts → simulator.ts} +4 -4
- package/types/{animation.ts → animation.d.ts} +24 -8
- package/types/{automation.ts → automation.d.ts} +16 -20
- package/types/{brick-base.ts → brick-base.d.ts} +1 -1
- package/types/bricks/{Camera.ts → Camera.d.ts} +8 -8
- package/types/bricks/{Chart.ts → Chart.d.ts} +4 -4
- package/types/bricks/{GenerativeMedia.ts → GenerativeMedia.d.ts} +15 -15
- package/types/bricks/{Icon.ts → Icon.d.ts} +7 -7
- package/types/bricks/{Image.ts → Image.d.ts} +21 -9
- package/types/bricks/{Items.ts → Items.d.ts} +7 -7
- package/types/bricks/{Lottie.ts → Lottie.d.ts} +10 -10
- package/types/bricks/{Maps.ts → Maps.d.ts} +11 -11
- package/types/bricks/{QrCode.ts → QrCode.d.ts} +7 -7
- package/types/bricks/{Rect.ts → Rect.d.ts} +7 -7
- package/types/bricks/{RichText.ts → RichText.d.ts} +12 -9
- package/types/bricks/{Rive.ts → Rive.d.ts} +9 -9
- package/types/bricks/Scene3D.d.ts +676 -0
- package/types/bricks/{Sketch.ts → Sketch.d.ts} +6 -6
- package/types/bricks/{Slideshow.ts → Slideshow.d.ts} +7 -7
- package/types/bricks/{Svg.ts → Svg.d.ts} +7 -7
- package/types/bricks/{Text.ts → Text.d.ts} +9 -9
- package/types/bricks/{TextInput.ts → TextInput.d.ts} +10 -10
- package/types/bricks/{Video.ts → Video.d.ts} +12 -12
- package/types/bricks/{VideoStreaming.ts → VideoStreaming.d.ts} +10 -10
- package/types/bricks/{WebRtcStream.ts → WebRtcStream.d.ts} +1 -1
- package/types/bricks/{WebView.ts → WebView.d.ts} +4 -4
- package/types/bricks/{index.ts → index.d.ts} +1 -0
- package/types/{common.ts → common.d.ts} +3 -6
- package/types/data-calc-command/base.d.ts +57 -0
- package/types/data-calc-command/collection.d.ts +418 -0
- package/types/data-calc-command/color.d.ts +432 -0
- package/types/data-calc-command/constant.d.ts +50 -0
- package/types/data-calc-command/datetime.d.ts +147 -0
- package/types/data-calc-command/file.d.ts +129 -0
- package/types/data-calc-command/index.d.ts +13 -0
- package/types/data-calc-command/iteratee.d.ts +23 -0
- package/types/data-calc-command/logictype.d.ts +190 -0
- package/types/data-calc-command/math.d.ts +275 -0
- package/types/data-calc-command/object.d.ts +119 -0
- package/types/data-calc-command/sandbox.d.ts +66 -0
- package/types/data-calc-command/string.d.ts +407 -0
- package/types/{data-calc.ts → data-calc.d.ts} +1 -0
- package/types/{data.ts → data.d.ts} +4 -2
- package/types/generators/{Assistant.ts → Assistant.d.ts} +19 -0
- package/types/generators/{LlmGgml.ts → LlmGgml.d.ts} +43 -1
- package/types/generators/{LlmMlx.ts → LlmMlx.d.ts} +1 -0
- package/types/generators/{RerankerGgml.ts → RerankerGgml.d.ts} +5 -1
- package/types/generators/{SoundRecorder.ts → SoundRecorder.d.ts} +10 -1
- package/types/generators/{SpeechToTextGgml.ts → SpeechToTextGgml.d.ts} +6 -1
- package/types/generators/{SttAppleBuiltin.ts → SttAppleBuiltin.d.ts} +27 -4
- package/types/generators/{ThermalPrinter.ts → ThermalPrinter.d.ts} +9 -7
- package/types/generators/{VadGgml.ts → VadGgml.d.ts} +12 -2
- package/types/{subspace.ts → subspace.d.ts} +1 -1
- package/utils/__tests__/calc.test.js +25 -0
- package/utils/__tests__/id.test.js +154 -0
- package/utils/calc.ts +5 -1
- package/utils/data.ts +5 -7
- package/utils/event-props.ts +17 -0
- package/utils/id.ts +109 -56
- package/skills/bricks-ctor/rules/buttress.md +0 -156
- package/skills/bricks-ctor/rules/data-calculation.md +0 -209
- package/skills/bricks-design/LICENSE.txt +0 -180
- package/types/data-calc-command.ts +0 -7005
- /package/skills/bricks-ctor/{rules → references}/local-sync.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/media-flow.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/remote-data-bank.md +0 -0
- /package/skills/bricks-ctor/{rules → references}/standby-transition.md +0 -0
- /package/types/{canvas.ts → canvas.d.ts} +0 -0
- /package/types/{data-calc-script.ts → data-calc-script.d.ts} +0 -0
- /package/types/generators/{AlarmClock.ts → AlarmClock.d.ts} +0 -0
- /package/types/generators/{BleCentral.ts → BleCentral.d.ts} +0 -0
- /package/types/generators/{BlePeripheral.ts → BlePeripheral.d.ts} +0 -0
- /package/types/generators/{CanvasMap.ts → CanvasMap.d.ts} +0 -0
- /package/types/generators/{CastlesPay.ts → CastlesPay.d.ts} +0 -0
- /package/types/generators/{DataBank.ts → DataBank.d.ts} +0 -0
- /package/types/generators/{File.ts → File.d.ts} +0 -0
- /package/types/generators/{GraphQl.ts → GraphQl.d.ts} +0 -0
- /package/types/generators/{Http.ts → Http.d.ts} +0 -0
- /package/types/generators/{HttpServer.ts → HttpServer.d.ts} +0 -0
- /package/types/generators/{Information.ts → Information.d.ts} +0 -0
- /package/types/generators/{Intent.ts → Intent.d.ts} +0 -0
- /package/types/generators/{Iterator.ts → Iterator.d.ts} +0 -0
- /package/types/generators/{Keyboard.ts → Keyboard.d.ts} +0 -0
- /package/types/generators/{LlmAnthropicCompat.ts → LlmAnthropicCompat.d.ts} +0 -0
- /package/types/generators/{LlmAppleBuiltin.ts → LlmAppleBuiltin.d.ts} +0 -0
- /package/types/generators/{LlmMediaTekNeuroPilot.ts → LlmMediaTekNeuroPilot.d.ts} +0 -0
- /package/types/generators/{LlmOnnx.ts → LlmOnnx.d.ts} +0 -0
- /package/types/generators/{LlmOpenAiCompat.ts → LlmOpenAiCompat.d.ts} +0 -0
- /package/types/generators/{LlmQualcommAiEngine.ts → LlmQualcommAiEngine.d.ts} +0 -0
- /package/types/generators/{Mcp.ts → Mcp.d.ts} +0 -0
- /package/types/generators/{McpServer.ts → McpServer.d.ts} +0 -0
- /package/types/generators/{MediaFlow.ts → MediaFlow.d.ts} +0 -0
- /package/types/generators/{MqttBroker.ts → MqttBroker.d.ts} +0 -0
- /package/types/generators/{MqttClient.ts → MqttClient.d.ts} +0 -0
- /package/types/generators/{Question.ts → Question.d.ts} +0 -0
- /package/types/generators/{RealtimeTranscription.ts → RealtimeTranscription.d.ts} +0 -0
- /package/types/generators/{SerialPort.ts → SerialPort.d.ts} +0 -0
- /package/types/generators/{SoundPlayer.ts → SoundPlayer.d.ts} +0 -0
- /package/types/generators/{SpeechToTextOnnx.ts → SpeechToTextOnnx.d.ts} +0 -0
- /package/types/generators/{SpeechToTextPlatform.ts → SpeechToTextPlatform.d.ts} +0 -0
- /package/types/generators/{SqLite.ts → SqLite.d.ts} +0 -0
- /package/types/generators/{Step.ts → Step.d.ts} +0 -0
- /package/types/generators/{Tcp.ts → Tcp.d.ts} +0 -0
- /package/types/generators/{TcpServer.ts → TcpServer.d.ts} +0 -0
- /package/types/generators/{TextToSpeechAppleBuiltin.ts → TextToSpeechAppleBuiltin.d.ts} +0 -0
- /package/types/generators/{TextToSpeechGgml.ts → TextToSpeechGgml.d.ts} +0 -0
- /package/types/generators/{TextToSpeechOnnx.ts → TextToSpeechOnnx.d.ts} +0 -0
- /package/types/generators/{TextToSpeechOpenAiLike.ts → TextToSpeechOpenAiLike.d.ts} +0 -0
- /package/types/generators/{Tick.ts → Tick.d.ts} +0 -0
- /package/types/generators/{Udp.ts → Udp.d.ts} +0 -0
- /package/types/generators/{VadOnnx.ts → VadOnnx.d.ts} +0 -0
- /package/types/generators/{VadTraditional.ts → VadTraditional.d.ts} +0 -0
- /package/types/generators/{VectorStore.ts → VectorStore.d.ts} +0 -0
- /package/types/generators/{Watchdog.ts → Watchdog.d.ts} +0 -0
- /package/types/generators/{WebCrawler.ts → WebCrawler.d.ts} +0 -0
- /package/types/generators/{WebRtc.ts → WebRtc.d.ts} +0 -0
- /package/types/generators/{WebSocket.ts → WebSocket.d.ts} +0 -0
- /package/types/generators/{index.ts → index.d.ts} +0 -0
- /package/types/{index.ts → index.d.ts} +0 -0
- /package/types/{switch.ts → switch.d.ts} +0 -0
- /package/types/{system.ts → system.d.ts} +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Pressable Composition
|
|
2
|
+
|
|
3
|
+
The composition decision and the wiring decision are the same decision. Affordance — *can I press this?* — is a UX signal the user reads off the screen. Pressable configuration is how that signal lands at the runtime. Get the configuration wrong and the screen lies to the user: a tile looks pressable but isn't, or is pressable but only on certain pixels. Either way, the user fights the design.
|
|
4
|
+
|
|
5
|
+
This file covers the configuration side. For the affordance discipline above it (when a thing should look pressable in the first place, how it joins the 7-step journey), see [`user-journey.md`](user-journey.md) step 1 and the relevant archetype rule set in [`interaction-archetypes.md`](interaction-archetypes.md).
|
|
6
|
+
|
|
7
|
+
The single principle: **every composite (multi-element) button needs a deliberate `on_press` decision per Brick, with `pressable` set explicitly wherever press chains layer.** The runtime auto-bypasses safe cases (see below), so the trivial wrapper case works without ceremony — but the design intent should still be readable from the configuration when chains compete.
|
|
8
|
+
|
|
9
|
+
There is no single mandated shape. Many arrangements work. The discipline is making the decision explicit where presses can collide, not following a recipe.
|
|
10
|
+
|
|
11
|
+
## The values
|
|
12
|
+
|
|
13
|
+
`Brick.property.pressable` accepts:
|
|
14
|
+
|
|
15
|
+
- `'enabled'` — Brick receives press events; if it has an `on_press` chain, the chain runs.
|
|
16
|
+
- `'disabled'` — Brick does not receive press events even if `on_press` is configured. Useful for runtime-disabled state via DataLink.
|
|
17
|
+
- `'bypass'` — Brick is transparent to press events; the press passes through to whatever sits behind/around it.
|
|
18
|
+
|
|
19
|
+
**Runtime auto-bypass.** Pressable-capable Bricks — `BrickText`, `BrickImage`, `BrickIcon`, `BrickRect`, `BrickSvg`, `BrickLottie`, `BrickRichText`, `BrickVideo`, `BrickVideoStreaming`, `BrickQRCode`, `BrickMaps`, `BrickGenerativeMedia` — with no configured press events (no `on_press`, no `on_focus`, no press/focus outlets feeding Switches or Animations) are auto-bypassed: the runtime renders them with `pointerEvents='none'` so taps fall through to the Brick underneath. A user-set `pressable` value always wins over auto-bypass.
|
|
20
|
+
|
|
21
|
+
**Non-pressable families** — `BrickTextInput`, `BrickCamera`, `BrickSlideshow`, `BrickItems`, `BrickChart`, `BrickWebView`, `BrickRive` — manage their own interaction internally and are not auto-bypassed. Their pressable behavior is unchanged.
|
|
22
|
+
|
|
23
|
+
`Brick.events.on_press` is the standard press handler — an array of `EventAction`s that runs on tap.
|
|
24
|
+
|
|
25
|
+
## Common arrangements
|
|
26
|
+
|
|
27
|
+
### Text-only press surface
|
|
28
|
+
|
|
29
|
+
Simplest, most BRICKS-idiomatic.
|
|
30
|
+
|
|
31
|
+
- One `BrickText` with an `on_press` chain.
|
|
32
|
+
- The entire press surface is the text run itself.
|
|
33
|
+
|
|
34
|
+
Use when: the visual is the text and only the text. Branding-light text links, in-flow CTAs.
|
|
35
|
+
|
|
36
|
+
### Wrapped region (background, border, padding)
|
|
37
|
+
|
|
38
|
+
The moment you add a `BrickRect` (or any shape) as a tile, card, button background, or padded container, the press intent moves to the wrapper.
|
|
39
|
+
|
|
40
|
+
- `BrickRect` carries `on_press`.
|
|
41
|
+
- Inner Bricks (the text label, an icon, an image) sit on top with no press events of their own.
|
|
42
|
+
- The runtime auto-bypasses pressable-capable inner Bricks without press events, so the wrapper's chain fires regardless of where on the tile the user taps. No explicit `pressable: 'bypass'` is required for the common case.
|
|
43
|
+
- Setting `pressable: 'bypass'` on the inner Bricks is still good practice when the child is purely decorative: it documents the design and protects against later edits that wire press- or focus-driven effects onto the child (e.g., a Switch fed by a press outlet) and silently break the tile.
|
|
44
|
+
|
|
45
|
+
Use when: the press surface includes background, border, padding, or composition beyond a single text run.
|
|
46
|
+
|
|
47
|
+
### Composite tile (image + text + meta, all pressable as one)
|
|
48
|
+
|
|
49
|
+
Same shape as the wrapped region, with more children.
|
|
50
|
+
|
|
51
|
+
- Backing `BrickRect` carries `on_press`.
|
|
52
|
+
- All visual children — image, headline, subhead, icon, meta — auto-bypass when they carry no press events of their own.
|
|
53
|
+
- The user perceives one tile; the runtime sees one press surface.
|
|
54
|
+
- Explicit `pressable: 'bypass'` per child remains a clarity discipline, especially in tile layouts that are duplicated and later edited — a stray press handler on one child would otherwise break that tile silently.
|
|
55
|
+
|
|
56
|
+
Use when: a card, a row in a list (note: `Items` Brick has its own row press model — read the template), a media tile.
|
|
57
|
+
|
|
58
|
+
### Mixed-action composite (rare but real)
|
|
59
|
+
|
|
60
|
+
A composite where most of the surface does action A, but a small region does action B (e.g., a card whose body opens a detail view but whose corner heart icon toggles favorite).
|
|
61
|
+
|
|
62
|
+
- Backing `BrickRect` carries `on_press` for action A.
|
|
63
|
+
- Most children auto-bypass (no press events of their own).
|
|
64
|
+
- The exception child (heart icon) carries its own `on_press` for action B. Because it now has a press chain, auto-bypass is off and the icon owns its hit region.
|
|
65
|
+
- Caveat: ensure the exception child is large enough to hit reliably without colliding with the surrounding action A. If it's too small, users will fight it.
|
|
66
|
+
|
|
67
|
+
Use when: genuinely needed. If you find yourself reaching for this often, the design is probably trying to do too much per tile — split into clearer affordances.
|
|
68
|
+
|
|
69
|
+
## Failure modes
|
|
70
|
+
|
|
71
|
+
### Overlapping pressables
|
|
72
|
+
|
|
73
|
+
Two or more children of a composite each carry their own `on_press` (or a Switch/Animation fed by a press/focus outlet). Auto-bypass doesn't apply — each child has events. The runtime resolves taps to whichever Brick owns that pixel; in practice this means inconsistent behavior depending on where the user actually lands.
|
|
74
|
+
|
|
75
|
+
**Fix**: pick the press owner (usually the wrapper) and set the others to `pressable: 'bypass'` explicitly. The user-set value wins, so this forces the press through to the intended owner even when the loser Bricks have events.
|
|
76
|
+
|
|
77
|
+
### Hidden press handlers on inner Bricks
|
|
78
|
+
|
|
79
|
+
A composite that used to auto-bypass cleanly stops doing so after a Switch is wired to read a press or focus outlet on an inner Brick, or after an `on_press` is added to that child for an unrelated effect. The wrapper appears to lose hit area for that region without an obvious cause — auto-bypass is off the moment any press event is configured.
|
|
80
|
+
|
|
81
|
+
**Fix**: when you wire any press- or focus-driven effect on a Brick inside a composite, set its `pressable` explicitly. If the effect should run *and* the wrapper should still win the tap, use `pressable: 'bypass'` on the child and drive the effect from the wrapper's chain instead.
|
|
82
|
+
|
|
83
|
+
### Decorative overlay with unintended press chain
|
|
84
|
+
|
|
85
|
+
A decorative `BrickRect` (frame edge, gradient overlay, spacing tile) sits in front of a pressable region and has press events wired — usually a leftover Switch trigger or copy-pasted from a real button. Auto-bypass doesn't engage, and the overlay silently consumes taps.
|
|
86
|
+
|
|
87
|
+
**Fix**: any decorative overlay sitting in front of a pressable region should be `pressable: 'bypass'` explicitly. Treat "transparent in front of pressable" as a code smell that needs an explicit pressable decision — the runtime would auto-bypass a Brick with no press events, but the explicit value documents intent and survives future edits that wire effects into it.
|
|
88
|
+
|
|
89
|
+
### Phantom hover on no-touch hardware
|
|
90
|
+
|
|
91
|
+
Carried over from a web design with hover affordances — usually a Brick that brightens or shifts on press but the deployment is signage with no touch.
|
|
92
|
+
|
|
93
|
+
**Fix**: drop the affordance, or move it to a state driven by a different signal (proximity sensor, scheduled time, Data from a peripheral Generator).
|
|
94
|
+
|
|
95
|
+
### Web link semantics
|
|
96
|
+
|
|
97
|
+
Designers used to web habits sometimes add `<a>`-style underlines or color treatments expecting a navigation primitive.
|
|
98
|
+
|
|
99
|
+
**Fix**: BRICKS has no link semantic. `on_press` triggers `CHANGE_CANVAS` (internal navigation), a Generator (external action — open URL, dial number), or a Subspace action. The visual treatment is a design choice; the navigation primitive is a press handler.
|
|
100
|
+
|
|
101
|
+
### `pressable: 'disabled'` without visual signal
|
|
102
|
+
|
|
103
|
+
A Brick is set to `disabled` programmatically (e.g., during a pending submission) but the visual doesn't change. Users tap and nothing happens — looks broken.
|
|
104
|
+
|
|
105
|
+
**Fix**: pair `pressable: 'disabled'` with a Switch that alters appearance (opacity, color, an overlay) so disabled state is visible. This is also step 3 (immediate feedback) of the journey — the user must see that their tap was received *and* declined.
|
|
106
|
+
|
|
107
|
+
### Long-press misuse
|
|
108
|
+
|
|
109
|
+
`delayLongPress` is real — the runtime distinguishes tap from long-press. Useful for secondary actions on a primary tile (long-press to favourite, tap to open). Misuse: hiding *primary* actions behind a long-press because there isn't room for a button. Long-press is discoverable only to users who already know it; primary actions need primary affordances.
|
|
110
|
+
|
|
111
|
+
### Press feedback collapsed
|
|
112
|
+
|
|
113
|
+
A press registers — the wiring works — but there is no visual change at the moment of registration (no scale-down, no opacity flash, no Switch-driven highlight). This is the most common silent UX failure: the press works, the user can't tell, so they press again. See [`user-journey.md`](user-journey.md) step 3.
|
|
114
|
+
|
|
115
|
+
**Fix**: every `on_press` must pair with a visible state change at registration. A 100ms scale-down via Standby is enough; the user's eye needs to land on *something* moving.
|
|
116
|
+
|
|
117
|
+
## Decision checklist when authoring a composite button
|
|
118
|
+
|
|
119
|
+
For every Brick in the composite, answer:
|
|
120
|
+
|
|
121
|
+
1. Does this Brick own a press chain — `on_press`, or a Switch/Animation fed by a press/focus outlet?
|
|
122
|
+
2. If yes — should it own the tap, or should the wrapper own it?
|
|
123
|
+
|
|
124
|
+
If two or more Bricks answer "yes" on overlapping hit areas, set the loser(s) to `pressable: 'bypass'` explicitly. If only the wrapper has a press chain, pressable-capable inner Bricks auto-bypass — `pressable: 'bypass'` is recommended for clarity but not strictly required. Non-pressable family Bricks (TextInput, Camera, Slideshow, Items, Chart, WebView, Rive) follow their own interaction model and don't auto-bypass.
|
|
125
|
+
|
|
126
|
+
If any answer is "I haven't thought about it", the failure modes above are how it bites.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# The Universal User-Journey Spine
|
|
2
|
+
|
|
3
|
+
Every interaction in a BRICKS Application — touch press, peripheral input, remote command, external trigger — passes through the same seven steps. Skip any of them and the design has a hole the user will fall into. The shape is universal across QR scan, face detection, BLE proximity, NFC tap, payment terminal, voice mic, touch tile, operator remote.
|
|
4
|
+
|
|
5
|
+
This file is the spine. Anything that isn't here probably belongs in a more specific reference (transact intensification, peripheral particulars, archetype rule sets).
|
|
6
|
+
|
|
7
|
+
## The seven steps
|
|
8
|
+
|
|
9
|
+
### 1. Affordance — "Can I do something here?"
|
|
10
|
+
|
|
11
|
+
The user must know the channel exists before discovering it by chance. A QR-scan zone with no scan-here label, a BLE-proximity threshold with no "approach to begin" hint, a touch tile that looks decorative — all fail at step 1.
|
|
12
|
+
|
|
13
|
+
Affordance is the *invitation* to act. Without it, the rest of the journey is invisible.
|
|
14
|
+
|
|
15
|
+
**BRICKS-native expression:**
|
|
16
|
+
|
|
17
|
+
- A pressable region's visual treatment must declare "press me" — contrast, weight, a hint of motion via Standby Transition (gentle pulse), an icon. Use the runtime; don't rely on the user inferring touch from a flat Rect.
|
|
18
|
+
- Peripheral channels need an instructive Brick in the line-of-sight: a scan-frame Image, a microphone glyph that lights when listening is possible, a "tap card here" overlay.
|
|
19
|
+
- For ambient peripherals (BLE, camera detection), use a `valueHit` Data event on detection to *promote* the affordance — the screen invites the next step once the user is in range.
|
|
20
|
+
|
|
21
|
+
**Failure modes:**
|
|
22
|
+
|
|
23
|
+
- **Affordance hidden.** Pressable Brick visually indistinguishable from non-pressable. User walks past.
|
|
24
|
+
- **Affordance present but ambiguous.** "Touch screen" floating with no anchor — touch *what*?
|
|
25
|
+
- **Affordance promised, channel absent.** A scan frame drawn for a deployment that has no scanner.
|
|
26
|
+
- **Affordance for absent capability.** Hover affordance on a no-touch panel; web-style cursor pointer on touch hardware.
|
|
27
|
+
|
|
28
|
+
### 2. Instruction — "What do I do?"
|
|
29
|
+
|
|
30
|
+
Once the user is invited, they need to act. The instruction must be specific enough to act on without overspecifying. Match the user's likely mental model — a payment terminal is not a QR scanner; same Canvas should not pretend otherwise.
|
|
31
|
+
|
|
32
|
+
**BRICKS-native expression:**
|
|
33
|
+
|
|
34
|
+
- A short directive in `RichText` near the affordance: "Scan your QR code", "Tap to start", "Hold card to reader". Bind to a Data string for multilingual.
|
|
35
|
+
- Iconography or animation that demonstrates the action (a hand reaching toward the reader, a card moving toward the panel). One demo, not a five-step diagram.
|
|
36
|
+
- For unfamiliar peripherals (NFC, voice for new users), include a one-shot tutorial on first entry — a separate Canvas, not a permanent overlay.
|
|
37
|
+
|
|
38
|
+
**Failure modes:**
|
|
39
|
+
|
|
40
|
+
- **Instruction missing.** Affordance present, action unclear. Common with NFC and voice — users don't know they need to *speak* or *tap*.
|
|
41
|
+
- **Instruction overspecified.** Five steps for a one-step action.
|
|
42
|
+
- **Instruction in wrong language** for the actual user — pin to deployment locale.
|
|
43
|
+
- **Instruction stays after the action.** "Scan now" still on screen after the scan succeeded — confuses about whether to scan again.
|
|
44
|
+
|
|
45
|
+
### 3. Immediate feedback — "Did I do it?"
|
|
46
|
+
|
|
47
|
+
The instant between the user's action and the system's acknowledgement is the highest-anxiety moment in the flow. The user has *committed* — touched, scanned, tapped, spoken — and now needs proof their action registered.
|
|
48
|
+
|
|
49
|
+
This is non-negotiable. A flow that skips immediate feedback has a hole the user falls through every time.
|
|
50
|
+
|
|
51
|
+
**BRICKS-native expression:**
|
|
52
|
+
|
|
53
|
+
- A snap-cut visual change at the moment of registration (not a fade-in — fades read as ambient, snaps read as acknowledgement). Brick scale, opacity, or color change via Switch on the input event.
|
|
54
|
+
- A Standby Transition on a new Brick that enters at the moment of acknowledgement — a checkmark, a state badge, a progress indicator.
|
|
55
|
+
- For audio-enabled deployments, a short auditory cue at the moment of registration. Silent is OK; ambient music is not enough.
|
|
56
|
+
- If the registration takes any perceptible time (peripheral confirming, network round-trip), step 3 still fires first — the *system received* you — then step 4 takes over for the *processing* state.
|
|
57
|
+
|
|
58
|
+
**Failure modes:**
|
|
59
|
+
|
|
60
|
+
- **Feedback skipped, jumps to continuation.** User can't tell if their action registered or the Canvas just changed for unrelated reasons.
|
|
61
|
+
- **Feedback fades in slowly.** Fade reads as ambient, not as response. Use snap on the moment of registration.
|
|
62
|
+
- **Feedback on the wrong Canvas.** User taps, Canvas A doesn't react, Canvas B (the next one) loads after a beat — user assumes nothing happened and taps again.
|
|
63
|
+
- **Same feedback for success and failure.** A check-mark feedback for a payment that's still processing reads as success — user walks away from a partial transaction.
|
|
64
|
+
|
|
65
|
+
### 4. In-flight visibility — "Is it being handled?"
|
|
66
|
+
|
|
67
|
+
When the system needs time — peripheral confirming, network round-trip, Generator awaiting response — the user must know the system is working. Silent processing reads as broken.
|
|
68
|
+
|
|
69
|
+
**BRICKS-native expression:**
|
|
70
|
+
|
|
71
|
+
- A clear in-flight Brick set: spinner, progress indicator, animated state badge. Use `Animation` `runType: 'loop'` here — this is a legitimate loop use (continuous attention-draw signalling activity).
|
|
72
|
+
- The trigger affordance disables (`pressable: 'disabled'` or Switch-driven hide) so the user can't trigger again.
|
|
73
|
+
- Time-bounded: if the in-flight state can plausibly take > 5 seconds, surface a progress estimate or a continued-activity reassurance ("Still processing — this can take up to 30 seconds"). Don't leave the user staring at an unchanging spinner past their patience threshold.
|
|
74
|
+
- For peripherals: the peripheral's own state (Generator event from the device) should drive Switch transitions, not a guess at how long it'll take.
|
|
75
|
+
|
|
76
|
+
**Failure modes:**
|
|
77
|
+
|
|
78
|
+
- **No in-flight state at all.** Action triggered, Canvas frozen until result. User taps again; second action queues; chaos.
|
|
79
|
+
- **In-flight state visually identical to idle.** No motion, no change in tone — user can't tell whether the system is working or stalled.
|
|
80
|
+
- **In-flight state with no time-out.** System hangs; Canvas stays on "processing…" forever. Every in-flight state needs a Generator-driven failure path back to step 6.
|
|
81
|
+
- **In-flight state allows re-trigger.** User can press again, queues duplicate work — especially bad for transact flows.
|
|
82
|
+
|
|
83
|
+
### 5. Continuation — "What's next?"
|
|
84
|
+
|
|
85
|
+
The action succeeded; what now? The user needs to know whether they continue here, move to a next state, walk away, or hand over to a peripheral / operator.
|
|
86
|
+
|
|
87
|
+
**BRICKS-native expression:**
|
|
88
|
+
|
|
89
|
+
- A Canvas change is the most common continuation — Switch on the success Data event, new Canvas with the next prompt or the result.
|
|
90
|
+
- For flows that loop (slideshow, ambient signage), continuation can mean returning to attract / idle state.
|
|
91
|
+
- The continuation Canvas's hero Brick(s) share id with the previous Canvas's hero — Truth #3 auto-tween carries the user across with continuity, not by hard cut.
|
|
92
|
+
- If continuation is "stay here, do another thing," the prompt must update — the user shouldn't have to guess that they can act again.
|
|
93
|
+
|
|
94
|
+
**Failure modes:**
|
|
95
|
+
|
|
96
|
+
- **Continuation ambiguous.** Success acknowledged but the user is left on a static Canvas with no next-step prompt. They wait, then walk away.
|
|
97
|
+
- **Continuation surprises.** Success on Canvas A, but Canvas D loads next — the journey shifts in a way that didn't match the user's mental model.
|
|
98
|
+
- **Continuation lacks closure for some users.** A receipt flow that auto-advances after 3s before the user has read the confirmation.
|
|
99
|
+
|
|
100
|
+
### 6. Recovery — "What if it went wrong?"
|
|
101
|
+
|
|
102
|
+
Error states are first-class designed states, not afterthoughts. The recovery path tells the user what failed, why if relevant, and how to proceed — including the option to abandon.
|
|
103
|
+
|
|
104
|
+
**BRICKS-native expression:**
|
|
105
|
+
|
|
106
|
+
- A dedicated error Canvas (or a clearly distinct error state on the current Canvas, Switch-revealed). Not a tiny red text in a corner.
|
|
107
|
+
- The error declares the cause in user-language: "Card declined" not "ERR_TRANSACTION_REJECTED". "Couldn't read the code — try moving closer" not "Scanner timeout".
|
|
108
|
+
- The recovery offers a path — retry, alternate method, get help, abandon — never just states the failure.
|
|
109
|
+
- Form input retained across error — user shouldn't re-type after a validation fail.
|
|
110
|
+
- For peripherals, distinguish *user error* (action failed; retry) from *system error* (device unavailable; not the user's fault) from *abandonment* (timeout; resume or restart).
|
|
111
|
+
|
|
112
|
+
**Failure modes:**
|
|
113
|
+
|
|
114
|
+
- **Recovery as dead-end.** "Error. Please try again." with no path forward.
|
|
115
|
+
- **Recovery erases progress.** User restarts from step 1 of a 5-step flow after a single-step failure.
|
|
116
|
+
- **Recovery in unrelated language.** Technical error codes shown to end users.
|
|
117
|
+
- **Recovery missing for the silent failures.** Peripheral disconnected mid-flow with no recovery path — user assumed it'd just work.
|
|
118
|
+
- **Same recovery for all error types.** User can't distinguish "try again" from "the system is broken, walk away" from "you need to do something different".
|
|
119
|
+
|
|
120
|
+
### 7. Closure — "When can I leave?"
|
|
121
|
+
|
|
122
|
+
Especially in transact flows, the *past-the-point-of-no-return* moment must be explicit. The success state must declare the action complete in a way the user trusts before they walk away.
|
|
123
|
+
|
|
124
|
+
**BRICKS-native expression:**
|
|
125
|
+
|
|
126
|
+
- A success Canvas with weight — a confident headline, a check or stamp graphic, a printed-receipt analogue if applicable. Not a transient toast.
|
|
127
|
+
- The success Canvas must hold visibly for enough time that a careful user reads it (5–10 seconds for a transact closure; 2–3 for routine completion) before auto-advancing or resetting to idle.
|
|
128
|
+
- For ongoing actions (registered, will-be-notified), the closure must clarify the future: "You'll receive a confirmation by email" or "We'll call you when ready" with the expected wait clearly stated.
|
|
129
|
+
- For closures that have a physical artifact (receipt, ticket, badge), the artifact's presence is part of the closure — the Canvas waits for the printer event before declaring done.
|
|
130
|
+
|
|
131
|
+
**Failure modes:**
|
|
132
|
+
|
|
133
|
+
- **Closure transient.** Success shown for 800ms, auto-advances to idle. User mid-blink, never saw confirmation.
|
|
134
|
+
- **Closure ambiguous with in-flight.** Success badge in the same visual tone as the processing spinner — user uncertain whether they're done.
|
|
135
|
+
- **Closure missing for irreversible action.** Payment confirmed by terminal but no on-screen success — user walks away unsure, doubles up via a second tap.
|
|
136
|
+
- **Closure carries forward as a state the user can edit.** Success Canvas with input fields the user can change — implies the action isn't really committed.
|
|
137
|
+
|
|
138
|
+
## When the seven steps compress
|
|
139
|
+
|
|
140
|
+
Some interactions naturally collapse steps. Don't pad; collapse with intention.
|
|
141
|
+
|
|
142
|
+
- **Glance-archetype loops** (signage, attractor): the user doesn't act. Step 1 (affordance) collapses to "attention", step 5 (continuation) to "loop", steps 3, 4, 6, 7 don't apply. Only 1, 2, 5 are live.
|
|
143
|
+
- **Pure-monitor screens**: user doesn't act. Steps 1–4, 6, 7 collapse; the design is entirely about reading the state (see [`monitoring-screens.md`](monitoring-screens.md)).
|
|
144
|
+
- **One-step transact** (NFC tap-and-go without amount confirmation): steps 1–4 fire in rapid sequence, but each still must be present — affordance, instruction, registration feedback, in-flight visibility, even if the in-flight is <1 second.
|
|
145
|
+
|
|
146
|
+
Collapse means *fewer steps*, not *skipped steps*. A step you've skipped is a hole, not a compression.
|
|
147
|
+
|
|
148
|
+
## When the seven steps intensify (transact)
|
|
149
|
+
|
|
150
|
+
For payment / identity capture / safety-critical actions, the journey tightens:
|
|
151
|
+
|
|
152
|
+
- **Step 2 (instruction) gets a confirmation step bolted on** — "You're about to charge $42.50. Confirm?" — a designed checkpoint Canvas before commit.
|
|
153
|
+
- **Step 4 (in-flight visibility) cannot be skipped or hurried** — even a sub-second processing moment needs explicit visual presence. The cost of a missed in-flight is the user double-tapping a $42 charge.
|
|
154
|
+
- **Step 6 (recovery) gets explicit categories** — card declined / network error / user cancelled / timeout — each with its own recovery path. A generic "Try again" is insufficient.
|
|
155
|
+
- **Step 7 (closure) gets longer hold time + receipt** — the user must trust the transaction completed; cheap closures invite dispute.
|
|
156
|
+
|
|
157
|
+
This intensification is enough that transact flows often deserve a separate design pass; for v1 of this skill, treat it as the loudest application of the seven-step spine.
|
|
158
|
+
|
|
159
|
+
## How to use this file
|
|
160
|
+
|
|
161
|
+
When designing any interaction:
|
|
162
|
+
|
|
163
|
+
1. **Open the seven steps.** Walk the flow as the user would.
|
|
164
|
+
2. **For each step, name** what the user sees / what the system does / how feedback manifests / what fails. If you cannot fill all four for a step, that step is unfinished.
|
|
165
|
+
3. **Check the failure modes per step.** Each is a thing agents commonly skip. Caught here is cheap.
|
|
166
|
+
4. **Express in BRICKS primitives.** Each step has expression notes — which Brick families, which architecture truths (Truth #3 hero auto-tween across the journey; Truth #5 Generator async; Truth #7 Standby Transition for feedback).
|
|
167
|
+
|
|
168
|
+
The spine doesn't tell you what to design. It tells you which holes to *not have*.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# UX Critique
|
|
2
|
+
|
|
3
|
+
Verification proves the flow *runs*. UX critique proves the flow is *usable*. Both are required before declaring done; neither substitutes for the other.
|
|
4
|
+
|
|
5
|
+
UX critique runs in parallel with visual-design critique (`bricks-design/design-critique.md`). A Canvas can fail one and pass the other; both block ship.
|
|
6
|
+
|
|
7
|
+
This file is tiered. The same UX problem (e.g., closure missing) is **CRITICAL** on a payment kiosk and **MEDIUM** on a museum signage loop. Critique fails ship when CRITICAL or HIGH items go unaddressed in their applicable tiers.
|
|
8
|
+
|
|
9
|
+
## How to run the pass
|
|
10
|
+
|
|
11
|
+
1. **Open the journey.** Walk every interaction step-by-step using `user-journey.md` as the spine.
|
|
12
|
+
2. **For each Canvas / Brick group, classify by deployment risk** (table below). The classification determines which tier of checks apply.
|
|
13
|
+
3. **Run the checks in the applicable tier(s).** CRITICAL items always run; HIGH applies broadly; MEDIUM and LOW depend on classification.
|
|
14
|
+
4. **Fix every CRITICAL miss and every HIGH miss.** Surface MEDIUM and LOW misses in the trade-off note if not addressed.
|
|
15
|
+
5. **Re-verify after fixes.** Don't accumulate fix debt.
|
|
16
|
+
|
|
17
|
+
## Risk classification by deployment shape
|
|
18
|
+
|
|
19
|
+
| Deployment shape | Risk profile | Tier weighting |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| Payment / identity capture / safety-critical | CRITICAL-dense — every transact discipline must pass | CRITICAL + HIGH + MEDIUM all apply |
|
|
22
|
+
| Self-service interactive (ordering, check-in, configuration) | HIGH-dense — journey completeness, idle reset, recovery paths | CRITICAL + HIGH apply; MEDIUM where relevant |
|
|
23
|
+
| Monitoring / dashboard / control room | HIGH-dense — alarm hierarchy, stale-data trust, calm/detect/demand | CRITICAL + HIGH apply for alarm states |
|
|
24
|
+
| Browse / wayfinding / menu board | MEDIUM-dense — legibility, comparison clarity, sparse states | HIGH legibility, MEDIUM elsewhere |
|
|
25
|
+
| Glanceable signage / dwell / ambient loop | LOW-dense — main checks are legibility and rhythm | HIGH legibility, LOW elsewhere |
|
|
26
|
+
|
|
27
|
+
## CRITICAL — blocks ship
|
|
28
|
+
|
|
29
|
+
Failure on any of these is sufficient grounds to declare the work mid-iteration.
|
|
30
|
+
|
|
31
|
+
### CR-1. The user-journey spine is whole
|
|
32
|
+
|
|
33
|
+
Every interaction must have all seven steps present (or deliberately compressed; see `user-journey.md` § "When the seven steps compress"). Most-common holes:
|
|
34
|
+
|
|
35
|
+
- **Immediate feedback missing** (step 3). User taps, nothing visible, taps again.
|
|
36
|
+
- **In-flight visibility missing** (step 4). Action triggered, screen frozen, user re-triggers.
|
|
37
|
+
- **Recovery as dead-end** (step 6). Error Canvas with no path forward.
|
|
38
|
+
- **Closure ambiguous or missing** (step 7). Success state transient or unclear.
|
|
39
|
+
|
|
40
|
+
**Must Have:** every interaction has a designed and verified affordance, instruction, feedback, in-flight state, continuation, recovery, closure — or deliberate documented collapse.
|
|
41
|
+
**Anti-Pattern:** journey skipping any step on the assumption "it's fast enough not to need".
|
|
42
|
+
|
|
43
|
+
### CR-2. Transact has its intensification
|
|
44
|
+
|
|
45
|
+
For payment / identity / safety-critical flows, the seven steps must be tightened (`user-journey.md` § "When the seven steps intensify"):
|
|
46
|
+
|
|
47
|
+
- Confirmation step before commit.
|
|
48
|
+
- In-flight visibility non-skippable.
|
|
49
|
+
- Recovery categorised (declined / network / cancelled / timeout).
|
|
50
|
+
- Closure with weight and hold time.
|
|
51
|
+
|
|
52
|
+
**Must Have:** transact closure holds ≥ 5 seconds, success unmistakable, peripheral state visible.
|
|
53
|
+
**Anti-Pattern:** "Process payment in one tap" with no confirmation; closure that auto-advances in < 2s.
|
|
54
|
+
|
|
55
|
+
### CR-3. Monitoring has demand discipline
|
|
56
|
+
|
|
57
|
+
For dashboards / monitor screens with alarm capability:
|
|
58
|
+
|
|
59
|
+
- Calm / detect / demand are three distinct designed states.
|
|
60
|
+
- Demand state stands out unambiguously; not a slight colour change.
|
|
61
|
+
- Multiple concurrent demand states have priority ordering already designed.
|
|
62
|
+
- Stale-data is visibly declared per source.
|
|
63
|
+
- Color is never the only encoding of severity.
|
|
64
|
+
|
|
65
|
+
**Must Have:** alarm-state preview captured and reviewed; squint-test passes (alarm visible from peripheral vision); monochrome preview still distinguishes severity tiers.
|
|
66
|
+
**Anti-Pattern:** alarm styled identically to detect-state; "stale" indicator at 8pt in corner; severity by colour alone.
|
|
67
|
+
|
|
68
|
+
### CR-4. Accessibility floors met for the audience
|
|
69
|
+
|
|
70
|
+
The seven accessibility disciplines (`accessibility.md`) apply at deployment-relative floors. CRITICAL when the audience includes:
|
|
71
|
+
|
|
72
|
+
- Reduced-vision users (contrast, scale, color-not-only).
|
|
73
|
+
- Reduced-motor users (touch targets, no precise gestures, no time-pressure auto-cancels).
|
|
74
|
+
- Sensitive-to-motion users (reduced-motion path available).
|
|
75
|
+
- Multilingual audience (strings via Data; type scale accommodates longest translation).
|
|
76
|
+
|
|
77
|
+
**Must Have:** contrast verified under deployment-actual lighting; touch targets verified with real-finger / real-hardware testing; severity / status encoded by shape + color + label.
|
|
78
|
+
**Anti-Pattern:** WCAG-compliance-on-desktop-monitor as the audit (deployment is sunlit signage at 4m); time-pressure UI on a venue that includes slow / hesitant users.
|
|
79
|
+
|
|
80
|
+
### CR-5. Idle reset is clean
|
|
81
|
+
|
|
82
|
+
For self-service interactive deployments:
|
|
83
|
+
|
|
84
|
+
- Idle timeout exists and is calibrated to the use pattern.
|
|
85
|
+
- Idle reset clears flow state — no half-built order persists; no name on screen; no half-confirmed action.
|
|
86
|
+
- Reset returns to a designed idle / attractor state, not to "whatever Canvas was loaded".
|
|
87
|
+
|
|
88
|
+
**Must Have:** verified idle reset behaviour via Automation or live test; verified that no leaked state persists to next session.
|
|
89
|
+
**Anti-Pattern:** idle = last interact Canvas; idle that requires operator to manually reset.
|
|
90
|
+
|
|
91
|
+
## HIGH — must fix before launch
|
|
92
|
+
|
|
93
|
+
### H-1. Affordance is unambiguous
|
|
94
|
+
|
|
95
|
+
Every pressable Brick declares "press me" visually — contrast, weight, motion, or icon. Affordance vocabulary is consistent across the deployment.
|
|
96
|
+
|
|
97
|
+
**Must Have:** pressable tiles visually distinct from non-pressable; squint test confirms which Bricks are interactive.
|
|
98
|
+
**Anti-Pattern:** identical-looking tiles where some are pressable and some aren't; pressable Bricks with no visual distinction from background.
|
|
99
|
+
|
|
100
|
+
### H-2. Press feedback at registration
|
|
101
|
+
|
|
102
|
+
Every `on_press` produces a visible state change at the moment of registration — scale, opacity, color, or new Brick entry via Standby Transition. Not a fade-in.
|
|
103
|
+
|
|
104
|
+
**Must Have:** Brick under press visibly responds within 100ms (effectively snap, not fade).
|
|
105
|
+
**Anti-Pattern:** press registers, no visible change; press relies on Canvas-change-only feedback (user perceives lag).
|
|
106
|
+
|
|
107
|
+
### H-3. Flow states designed, not skipped
|
|
108
|
+
|
|
109
|
+
Idle / loading / empty / error / boot / maintenance (as applicable to the deployment) are designed states, each with verified preview.
|
|
110
|
+
|
|
111
|
+
**Must Have:** every applicable state's preview captured and reviewed; failure modes per state checked against `flow-states.md`.
|
|
112
|
+
**Anti-Pattern:** "empty state is fine, the list is always populated" without testing the empty case; error state in 8pt corner text; boot = loading spinner.
|
|
113
|
+
|
|
114
|
+
### H-4. Hero continuity across journey Canvases
|
|
115
|
+
|
|
116
|
+
For multi-Canvas flows, hero Bricks share ids across Canvases — chrome (logo, progress indicator, time, back action) carries via Truth #3 auto-tween.
|
|
117
|
+
|
|
118
|
+
**Must Have:** primary chrome elements identifiable in the same screen positions across the flow's Canvases.
|
|
119
|
+
**Anti-Pattern:** every Canvas remounts its full Brick set; user loses spatial mental model between steps.
|
|
120
|
+
|
|
121
|
+
### H-5. Error recovery has a path
|
|
122
|
+
|
|
123
|
+
Every error state offers retry, alternate method, abandon, or help — never a dead-end. Different error categories get distinguishable recovery treatments.
|
|
124
|
+
|
|
125
|
+
**Must Have:** each error path verified end-to-end via Automation or live test.
|
|
126
|
+
**Anti-Pattern:** "Error. Please try again." with no retry mechanism; generic recovery for all error types.
|
|
127
|
+
|
|
128
|
+
### H-6. Stale-data trust
|
|
129
|
+
|
|
130
|
+
For monitoring / live-data screens, every dynamic source has a visibly declared staleness state or a visible "last update" relative-time indicator.
|
|
131
|
+
|
|
132
|
+
**Must Have:** staleness visible in peripheral vision; stale value distinguishable from current value.
|
|
133
|
+
**Anti-Pattern:** dynamic value with no age indication; "last update" in 8pt absolute timestamp.
|
|
134
|
+
|
|
135
|
+
### H-7. Predictable navigation
|
|
136
|
+
|
|
137
|
+
Navigation affordances (back, next, home) sit consistently across Canvases. Idle returns to the same idle Canvas every time.
|
|
138
|
+
|
|
139
|
+
**Must Have:** back / next affordance positions verified across Canvases; idle reset produces identical idle Canvas every time.
|
|
140
|
+
**Anti-Pattern:** back button at top-left on Canvas 2, bottom-right on Canvas 3; idle rotates such that the "anchor" element moves.
|
|
141
|
+
|
|
142
|
+
## MEDIUM — impacts polish; prioritize by user impact
|
|
143
|
+
|
|
144
|
+
### M-1. Instruction matches user mental model
|
|
145
|
+
|
|
146
|
+
Action instructions ("Scan your code", "Tap to start", "Pay now") match the user's likely understanding; jargon avoided; language locale-matched.
|
|
147
|
+
|
|
148
|
+
**Must Have:** wording reviewed for the actual audience; jargon removed.
|
|
149
|
+
**Anti-Pattern:** "Initiate authentication sequence" on a public kiosk; English-only instruction on a multilingual venue.
|
|
150
|
+
|
|
151
|
+
### M-2. Continuation is non-surprising
|
|
152
|
+
|
|
153
|
+
The Canvas the user lands on after an action matches their expectation. Big jumps in journey context happen only with explicit transition.
|
|
154
|
+
|
|
155
|
+
**Must Have:** continuation Canvas reviewed for context preservation; hero continuity in place.
|
|
156
|
+
**Anti-Pattern:** success on Canvas A jumps to Canvas D mid-flow; user loses context.
|
|
157
|
+
|
|
158
|
+
### M-3. Closure proportional to stakes
|
|
159
|
+
|
|
160
|
+
Success closure hold time and visual weight match the action's stakes. Routine completions get brief acknowledgement; transact completions get held visible.
|
|
161
|
+
|
|
162
|
+
**Must Have:** closure hold time tuned to action stakes; auto-advance only after enough time for the user to read.
|
|
163
|
+
**Anti-Pattern:** 1.5s closure for a transact flow; 8s closure for a "saved to favorites" toast.
|
|
164
|
+
|
|
165
|
+
### M-4. Density rhythm in browse / dwell
|
|
166
|
+
|
|
167
|
+
For browse / dwell deployments, content rotation has variation in density / framing / colour rhythm across Canvases or slides. Three identical-rhythm Canvases in a row is a tell. (Cross-reference: `bricks-design/design-critique.md` density-rhythm collapse.)
|
|
168
|
+
|
|
169
|
+
**Must Have:** rhythm variation across the sequence; no three identical-archetype Canvases in a row.
|
|
170
|
+
**Anti-Pattern:** ten Canvases that all read as "headline + bullet list".
|
|
171
|
+
|
|
172
|
+
### M-5. Audio cues calibrated (when hardware allows)
|
|
173
|
+
|
|
174
|
+
If the deployment uses audio, volume / timing / time-of-day awareness is intentional.
|
|
175
|
+
|
|
176
|
+
**Must Have:** audio volume / scheduling reviewed; success / alarm cues distinguishable from ambient.
|
|
177
|
+
**Anti-Pattern:** constant-volume cues regardless of time; audio-only confirmation (no visual pair).
|
|
178
|
+
|
|
179
|
+
### M-6. Reduced-motion path available (if relevant)
|
|
180
|
+
|
|
181
|
+
For deployments in clinical / safety / motion-sensitive contexts, a reduced-motion path exists.
|
|
182
|
+
|
|
183
|
+
**Must Have:** `reduceMotion` Data flag wired where applicable; verified reduced-motion preview.
|
|
184
|
+
**Anti-Pattern:** continuous-motion design with no reduction path for a clinical deployment.
|
|
185
|
+
|
|
186
|
+
## LOW — context-dependent
|
|
187
|
+
|
|
188
|
+
### L-1. Polish on idle / attractor presence
|
|
189
|
+
|
|
190
|
+
For deployments where idle dominates: idle is *inviting*, not just *running*.
|
|
191
|
+
|
|
192
|
+
**Must Have for high-traffic public deployments:** attractor sequence engages from peripheral vision.
|
|
193
|
+
**Anti-Pattern:** logo-only idle on a deployment whose purpose is to invite interaction.
|
|
194
|
+
|
|
195
|
+
### L-2. Maintenance state designed
|
|
196
|
+
|
|
197
|
+
If the deployment has scheduled downtime, a maintenance Canvas exists and distinguishes itself from error.
|
|
198
|
+
|
|
199
|
+
**Must Have for deployments with maintenance windows:** maintenance Canvas with return path.
|
|
200
|
+
**Anti-Pattern:** maintenance = black screen.
|
|
201
|
+
|
|
202
|
+
### L-3. Empty-state composition
|
|
203
|
+
|
|
204
|
+
Empty states explain the absence and suggest what populates.
|
|
205
|
+
|
|
206
|
+
**Must Have:** empty state designed rather than collapsed-whitespace.
|
|
207
|
+
**Anti-Pattern:** "—" or blank where content would be.
|
|
208
|
+
|
|
209
|
+
## Tier-by-tier checklist (running order)
|
|
210
|
+
|
|
211
|
+
Use this when running the pass:
|
|
212
|
+
|
|
213
|
+
**CRITICAL (always run):**
|
|
214
|
+
|
|
215
|
+
- [ ] User-journey spine whole (CR-1)
|
|
216
|
+
- [ ] Transact intensified if applicable (CR-2)
|
|
217
|
+
- [ ] Monitoring demand discipline if applicable (CR-3)
|
|
218
|
+
- [ ] Accessibility floors met (CR-4)
|
|
219
|
+
- [ ] Idle reset clean if applicable (CR-5)
|
|
220
|
+
|
|
221
|
+
**HIGH (always run except for glance / dwell loops):**
|
|
222
|
+
|
|
223
|
+
- [ ] Affordance unambiguous (H-1)
|
|
224
|
+
- [ ] Press feedback at registration (H-2)
|
|
225
|
+
- [ ] Flow states designed (H-3)
|
|
226
|
+
- [ ] Hero continuity across Canvases (H-4)
|
|
227
|
+
- [ ] Error recovery has a path (H-5)
|
|
228
|
+
- [ ] Stale-data trust if applicable (H-6)
|
|
229
|
+
- [ ] Predictable navigation (H-7)
|
|
230
|
+
|
|
231
|
+
**MEDIUM (run for interact / transact / browse):**
|
|
232
|
+
|
|
233
|
+
- [ ] Instruction matches mental model (M-1)
|
|
234
|
+
- [ ] Continuation non-surprising (M-2)
|
|
235
|
+
- [ ] Closure proportional (M-3)
|
|
236
|
+
- [ ] Density rhythm (M-4)
|
|
237
|
+
- [ ] Audio cues calibrated (M-5)
|
|
238
|
+
- [ ] Reduced-motion path (M-6)
|
|
239
|
+
|
|
240
|
+
**LOW (run for public-facing / high-dwell):**
|
|
241
|
+
|
|
242
|
+
- [ ] Idle / attractor polish (L-1)
|
|
243
|
+
- [ ] Maintenance state (L-2)
|
|
244
|
+
- [ ] Empty-state composition (L-3)
|
|
245
|
+
|
|
246
|
+
## Definition of UX done
|
|
247
|
+
|
|
248
|
+
A flow is UX-done when:
|
|
249
|
+
|
|
250
|
+
1. The CRITICAL tier passes — zero unaddressed CRITICAL items.
|
|
251
|
+
2. The HIGH tier passes — zero unaddressed HIGH items (or surfaced as accepted trade-off with rationale).
|
|
252
|
+
3. Applicable MEDIUM / LOW items either pass or are surfaced.
|
|
253
|
+
4. Every state (golden path + error categories + idle + empty if relevant) has been verified via preview screenshot or Automation, and reviewed.
|
|
254
|
+
5. Accessibility floors are met at the deployment-relative threshold, not at a desktop default.
|
|
255
|
+
|
|
256
|
+
If any item is unmet, the work is mid-iteration. Say so explicitly to the user; offer a precise list of what remains.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { extractCliErrorMessage } from '../_cli-error'
|
|
2
|
+
|
|
3
|
+
// bricks-project's tsconfig has no @types/jest, so declare the globals this test
|
|
4
|
+
// uses (mirrors tools/mcp-tools/__tests__/huggingface.test.ts).
|
|
5
|
+
declare const describe: (name: string, fn: () => void) => void
|
|
6
|
+
declare const it: (name: string, fn: () => void) => void
|
|
7
|
+
declare const expect: (actual: unknown) => { toBe: (expected: unknown) => void }
|
|
8
|
+
|
|
9
|
+
describe('extractCliErrorMessage', () => {
|
|
10
|
+
// Regression: the tools used to build this message inside the same try that
|
|
11
|
+
// wrapped JSON.parse, so the throw was caught by its own catch and replaced
|
|
12
|
+
// with the raw JSON blob. The human-readable message must survive.
|
|
13
|
+
it('extracts error.message from a JSON error body', () => {
|
|
14
|
+
const output = JSON.stringify({ error: { message: 'Conflict: config was modified' } })
|
|
15
|
+
expect(extractCliErrorMessage(output, 'Update failed')).toBe('Conflict: config was modified')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('extracts a string error from a JSON error body', () => {
|
|
19
|
+
expect(extractCliErrorMessage('{"error":"Boom"}', 'Pull failed')).toBe('Boom')
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('returns the raw output when it is not JSON', () => {
|
|
23
|
+
expect(extractCliErrorMessage('plain text failure', 'Release failed')).toBe(
|
|
24
|
+
'plain text failure',
|
|
25
|
+
)
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('falls back to the raw output when the JSON has no error field', () => {
|
|
29
|
+
expect(extractCliErrorMessage('{"ok":true}', 'Update failed')).toBe('{"ok":true}')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('falls back to the generic message when output is empty', () => {
|
|
33
|
+
expect(extractCliErrorMessage('', 'Update failed')).toBe('Update failed')
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Extract a human-readable message from a `bricks ... --json` failure payload.
|
|
2
|
+
//
|
|
3
|
+
// On failure the CLI prints `{ "error": { "message": "..." } }` (or the older
|
|
4
|
+
// `{ "error": "..." }`) to stdout/stderr. Earlier call sites built that message
|
|
5
|
+
// inside the same `try` that wrapped `JSON.parse`, so the `throw` was caught by
|
|
6
|
+
// its own `catch` and replaced with the raw JSON blob — the human-readable
|
|
7
|
+
// message never surfaced. Parsing here, outside any throw, avoids that trap.
|
|
8
|
+
export function extractCliErrorMessage(output: string, fallback: string): string {
|
|
9
|
+
try {
|
|
10
|
+
const { error } = JSON.parse(output)
|
|
11
|
+
const message = error?.message ?? error
|
|
12
|
+
if (typeof message === 'string' && message) return message
|
|
13
|
+
} catch {
|
|
14
|
+
// output is not JSON — fall through to the raw output below
|
|
15
|
+
}
|
|
16
|
+
return output || fallback
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
const auditLogIgnoreEntry = '.bricks/edits.jsonl'
|
|
5
|
+
|
|
6
|
+
// Ensure the project's .gitignore excludes the audit log. No-op when it is already
|
|
7
|
+
// ignored directly or via a broader `.bricks` rule.
|
|
8
|
+
const ensureAuditLogIgnored = async (projectDir: string) => {
|
|
9
|
+
const gitignorePath = path.join(projectDir, '.gitignore')
|
|
10
|
+
const content = await readFile(gitignorePath, 'utf8').catch((err: any) => {
|
|
11
|
+
if (err?.code === 'ENOENT') return ''
|
|
12
|
+
throw err
|
|
13
|
+
})
|
|
14
|
+
const ignored = content
|
|
15
|
+
.split(/\r?\n/)
|
|
16
|
+
.map((line) => line.trim())
|
|
17
|
+
.some((line) => line === auditLogIgnoreEntry || line === '.bricks/' || line === '.bricks')
|
|
18
|
+
if (ignored) return
|
|
19
|
+
|
|
20
|
+
const prefix = content && !content.endsWith('\n') ? '\n' : ''
|
|
21
|
+
await writeFile(
|
|
22
|
+
gitignorePath,
|
|
23
|
+
`${content}${prefix}\n# MCP entry-editing audit log\n${auditLogIgnoreEntry}\n`,
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Append one JSON record to `.bricks/edits.jsonl`, creating the directory and the
|
|
28
|
+
// gitignore entry as needed. Shared by the source-editing tools and `compile()` so
|
|
29
|
+
// every project mutation lands in the same audit log.
|
|
30
|
+
export const appendEditRecord = async (projectDir: string, record: Record<string, unknown>) => {
|
|
31
|
+
const bricksDir = path.join(projectDir, '.bricks')
|
|
32
|
+
await mkdir(bricksDir, { recursive: true })
|
|
33
|
+
await ensureAuditLogIgnored(projectDir)
|
|
34
|
+
await appendFile(path.join(bricksDir, 'edits.jsonl'), `${JSON.stringify(record)}\n`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Provenance stamped on every audit record: which agent/session produced the change.
|
|
38
|
+
export const editProvenance = () => ({
|
|
39
|
+
session: process.env.BRICKS_CTOR_SESSION_ID || process.env.CODEX_SESSION_ID,
|
|
40
|
+
agent: process.env.BRICKS_CTOR_AGENT_ID || process.env.USER,
|
|
41
|
+
})
|