@erclx/canon 4.41.0 → 4.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Serves candidate treatments of one decision from the running page, selected
|
|
4
|
+
* by a query parameter, with a switcher for moving between them.
|
|
5
|
+
*
|
|
6
|
+
* It exists because a decision about how something feels to cause cannot be
|
|
7
|
+
* settled from a capture or a recording, both of which are passive, and the
|
|
8
|
+
* operator has to drive the candidates themselves. Five decisions were served
|
|
9
|
+
* that way in one run, each with a hand-written parameter, switcher, and
|
|
10
|
+
* removal, none of which resembled the last.
|
|
11
|
+
*
|
|
12
|
+
* Renders nothing in a production build, and nothing in development either
|
|
13
|
+
* until the page is asked for an arm by name, so a page loaded without the
|
|
14
|
+
* parameter is what ships.
|
|
15
|
+
*
|
|
16
|
+
* An arm carries CSS when the decision is a treatment. When it is a value the
|
|
17
|
+
* page reads at runtime, a pace or a shader uniform, the arm carries no CSS
|
|
18
|
+
* and the module holding that value reads the active id off
|
|
19
|
+
* `document.documentElement.dataset[param]`.
|
|
20
|
+
*/
|
|
21
|
+
interface Arm {
|
|
22
|
+
/** Appears in the URL. Use `0` for what ships, so the baseline is an arm. */
|
|
23
|
+
readonly id: string
|
|
24
|
+
/** Names the arm and what it costs, since an arm with no cost is not one. */
|
|
25
|
+
readonly label: string
|
|
26
|
+
readonly css?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
/** The query parameter, which is also the dataset key an arm is read from. */
|
|
31
|
+
readonly param: string
|
|
32
|
+
readonly arms: readonly Arm[]
|
|
33
|
+
/**
|
|
34
|
+
* Where the switcher mounts, as a selector. A decision inside a modal needs
|
|
35
|
+
* its switcher inside that modal, since anything outside it sits under the
|
|
36
|
+
* backdrop. Defaults to the body.
|
|
37
|
+
*/
|
|
38
|
+
readonly mountInto?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { param, arms, mountInto } = Astro.props
|
|
42
|
+
|
|
43
|
+
// The component leaves the production tree rather than gating itself at
|
|
44
|
+
// runtime, so neither an arm nor the parameter can reach a reader.
|
|
45
|
+
const enabled = import.meta.env.DEV
|
|
46
|
+
|
|
47
|
+
// Held as a string and written with `set:html`. Inside an Astro expression a
|
|
48
|
+
// script's children are parsed the way JSX children are, so every brace in the
|
|
49
|
+
// source reads as an interpolation and the body ships as an unevaluated
|
|
50
|
+
// literal. It did, once, silently.
|
|
51
|
+
const SOURCE = `
|
|
52
|
+
const wanted = new URLSearchParams(location.search).get(param)
|
|
53
|
+
const active = arms.find((arm) => arm.id === wanted)
|
|
54
|
+
|
|
55
|
+
if (active) {
|
|
56
|
+
document.documentElement.dataset[param] = active.id
|
|
57
|
+
|
|
58
|
+
if (active.css) {
|
|
59
|
+
const style = document.createElement('style')
|
|
60
|
+
style.textContent = active.css
|
|
61
|
+
document.head.appendChild(style)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const render = () => {
|
|
65
|
+
const host = mountInto ? document.querySelector(mountInto) : document.body
|
|
66
|
+
if (!host) return
|
|
67
|
+
const bar = document.createElement('div')
|
|
68
|
+
bar.setAttribute('data-scenario-switcher', '')
|
|
69
|
+
for (const arm of arms) {
|
|
70
|
+
const link = document.createElement('a')
|
|
71
|
+
link.href = '?' + param + '=' + arm.id
|
|
72
|
+
link.textContent = arm.label
|
|
73
|
+
if (arm.id === active.id) link.setAttribute('data-current', 'true')
|
|
74
|
+
bar.appendChild(link)
|
|
75
|
+
}
|
|
76
|
+
host.appendChild(bar)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (document.readyState === 'loading') {
|
|
80
|
+
document.addEventListener('DOMContentLoaded', render)
|
|
81
|
+
} else {
|
|
82
|
+
render()
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`
|
|
86
|
+
|
|
87
|
+
const SWITCHER_CSS = `
|
|
88
|
+
/* Bottom left rather than bottom centre, which Astro's dev toolbar owns.
|
|
89
|
+
Centred, the toolbar sits over the switcher and swallows every click on it. */
|
|
90
|
+
[data-scenario-switcher] {
|
|
91
|
+
position: fixed;
|
|
92
|
+
left: 1.5rem;
|
|
93
|
+
bottom: 1.5rem;
|
|
94
|
+
z-index: 60;
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-wrap: wrap;
|
|
97
|
+
gap: 0.5rem;
|
|
98
|
+
max-width: calc(100vw - 3rem);
|
|
99
|
+
font: 500 13px system-ui, sans-serif;
|
|
100
|
+
}
|
|
101
|
+
[data-scenario-switcher] a {
|
|
102
|
+
padding: 0.4rem 0.8rem;
|
|
103
|
+
border: 1px solid rgb(128 128 128 / 0.4);
|
|
104
|
+
border-radius: 999px;
|
|
105
|
+
background: rgb(255 255 255 / 0.9);
|
|
106
|
+
color: #1a1815;
|
|
107
|
+
text-decoration: none;
|
|
108
|
+
white-space: nowrap;
|
|
109
|
+
}
|
|
110
|
+
[data-scenario-switcher] a[data-current='true'] {
|
|
111
|
+
background: #1a1815;
|
|
112
|
+
color: #f8f4ed;
|
|
113
|
+
}
|
|
114
|
+
`
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
{
|
|
118
|
+
enabled && (
|
|
119
|
+
<script
|
|
120
|
+
is:inline
|
|
121
|
+
define:vars={{ param, arms, mountInto }}
|
|
122
|
+
set:html={SOURCE}
|
|
123
|
+
/>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
{enabled && <style is:global set:html={SWITCHER_CSS} />}
|
|
@@ -58,3 +58,10 @@ Append to the `## Scripts` table:
|
|
|
58
58
|
## CI docs (extend)
|
|
59
59
|
|
|
60
60
|
In `.claude/context/ci.md`, the Typecheck row's assertion reads: `` `astro check` passes ``. The Build row's assertion reads: `` `astro build` succeeds ``.
|
|
61
|
+
|
|
62
|
+
## Scenario switcher
|
|
63
|
+
|
|
64
|
+
- `src/components/dev/scenarios.astro` ships as a golden config, always overwritten on sync, since it is toolkit-authored infrastructure rather than a file a project hand-edits.
|
|
65
|
+
- Import it into a page under test to drive candidate treatments of one decision by hand, selected by a query parameter, with a switcher for moving between them.
|
|
66
|
+
- Guarded by `import.meta.env.DEV`, so it renders nothing and ships nothing in a production build.
|
|
67
|
+
- Astro-only. It uses `is:inline`, `define:vars`, and `set:html`, which only the `.astro` file format parses. `vite-react` gets no equivalent until a real decision drives one.
|