storyink 0.4.0 → 0.6.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/README.md +1330 -146
  3. package/THIRD_PARTY_NOTICES.md +7 -0
  4. package/dist/chunks/index-8dqzx1yf.js +1061 -0
  5. package/dist/chunks/index-pc811tpa.js +19490 -0
  6. package/dist/cli.js +19112 -9387
  7. package/dist/core/index.js +90 -2
  8. package/dist/index.js +236 -13
  9. package/dist/node/index.js +24 -4
  10. package/dist/types/core/anchor.d.ts +6 -1
  11. package/dist/types/core/diff/coverage.d.ts +46 -0
  12. package/dist/types/core/diff/index.d.ts +8 -0
  13. package/dist/types/core/diff/lang.d.ts +7 -0
  14. package/dist/types/core/diff/parse.d.ts +20 -0
  15. package/dist/types/core/diff/resolve.d.ts +24 -0
  16. package/dist/types/core/diff/rows.d.ts +46 -0
  17. package/dist/types/core/diff/select.d.ts +35 -0
  18. package/dist/types/core/diff/types.d.ts +49 -0
  19. package/dist/types/core/index.d.ts +5 -1
  20. package/dist/types/core/layout/delta.d.ts +40 -0
  21. package/dist/types/core/layout/diffnode.d.ts +44 -0
  22. package/dist/types/core/layout/nodes.d.ts +7 -0
  23. package/dist/types/core/layout/overlays.d.ts +58 -0
  24. package/dist/types/core/layout/storyscan.d.ts +40 -0
  25. package/dist/types/core/page/blocks.d.ts +28 -0
  26. package/dist/types/core/page/css-story.d.ts +10 -0
  27. package/dist/types/core/page/css.d.ts +4 -0
  28. package/dist/types/core/page/index.d.ts +6 -0
  29. package/dist/types/core/page/prose.d.ts +31 -0
  30. package/dist/types/core/page/render.d.ts +30 -0
  31. package/dist/types/core/page/resolve.d.ts +15 -0
  32. package/dist/types/core/page/slides.d.ts +37 -0
  33. package/dist/types/core/page/targets.d.ts +32 -0
  34. package/dist/types/core/page/types.d.ts +241 -0
  35. package/dist/types/core/page/validate.d.ts +12 -0
  36. package/dist/types/core/render/App.d.ts +50 -1
  37. package/dist/types/core/render/Diagram.d.ts +8 -1
  38. package/dist/types/core/render/DiffView.d.ts +27 -0
  39. package/dist/types/core/render/Drawer.d.ts +47 -0
  40. package/dist/types/core/render/Hud.d.ts +22 -0
  41. package/dist/types/core/render/Narration.d.ts +25 -0
  42. package/dist/types/core/render/Story.d.ts +4 -0
  43. package/dist/types/core/render/css.d.ts +32 -4
  44. package/dist/types/core/render/figure.d.ts +17 -0
  45. package/dist/types/core/render/index.d.ts +13 -0
  46. package/dist/types/core/render/smil.d.ts +1 -1
  47. package/dist/types/core/scene.d.ts +133 -1
  48. package/dist/types/core/spec.d.ts +201 -8
  49. package/dist/types/core/story/acts.d.ts +108 -0
  50. package/dist/types/core/story/changes.d.ts +33 -0
  51. package/dist/types/core/story/compile.d.ts +20 -0
  52. package/dist/types/core/story/content-state.d.ts +15 -0
  53. package/dist/types/core/story/content.d.ts +59 -4
  54. package/dist/types/core/story/ease.d.ts +11 -0
  55. package/dist/types/core/story/narrate.d.ts +40 -0
  56. package/dist/types/core/story/state.d.ts +68 -2
  57. package/dist/types/core/story/types.d.ts +183 -11
  58. package/dist/types/core/suggest.d.ts +2 -0
  59. package/dist/types/generated/meta.d.ts +1 -1
  60. package/dist/types/generated/viewer.d.ts +1 -1
  61. package/dist/types/node/git.d.ts +33 -0
  62. package/dist/types/node/index.d.ts +4 -1
  63. package/dist/types/node/page.d.ts +26 -0
  64. package/dist/types/node/snapshot.d.ts +53 -3
  65. package/dist/types/theme/tokens.d.ts +89 -5
  66. package/dist/types/theme/tones.d.ts +38 -0
  67. package/docs/spec.md +767 -7
  68. package/examples/changes/auth-session-to-jwt.changes.sequence.json +35 -0
  69. package/examples/changes/auth-session-to-jwt.sequence.json +48 -0
  70. package/examples/changes/batch-email.changes.dataflow.json +83 -0
  71. package/examples/changes/batch-email.dataflow.json +97 -0
  72. package/examples/changes/cache-layer.architecture.json +47 -0
  73. package/examples/changes/etl-dedupe.dataflow.json +41 -0
  74. package/examples/changes/payment-retry.architecture.json +52 -0
  75. package/examples/changes/rate-limit-plugin.architecture.json +79 -0
  76. package/examples/changes/storyink-0.4.0.changes.json +8737 -0
  77. package/examples/changes/storyink-0.4.0.pr.json +417 -0
  78. package/examples/changes/storyink-core.architecture.json +57 -0
  79. package/examples/checkout-recovery.architecture.json +96 -0
  80. package/examples/pages/cache-plan.page.json +136 -0
  81. package/examples/pages/payment-retry.scrolly.page.json +32 -0
  82. package/examples/pages/pr-review.deck.page.json +98 -0
  83. package/examples/pages/pr-review.page.json +115 -0
  84. package/examples/pages/storyink-0.4.0.page.json +77 -0
  85. package/examples/pages/storyink-0.4.0.scrolly.page.json +32 -0
  86. package/examples/pages/storyink-recap.page.json +105 -0
  87. package/examples/stories/payment-retry.acts.architecture.json +340 -0
  88. package/examples/stories/request-colours.dataflow.json +68 -0
  89. package/examples/stories/secret-broker.architecture.json +126 -0
  90. package/package.json +8 -2
  91. package/schema/storyink.schema.json +1484 -106
  92. package/skill/SKILL.md +144 -2
  93. package/dist/chunks/index-e919a5xm.js +0 -10375
  94. package/dist/chunks/index-p76hjvyg.js +0 -557
  95. /package/dist/chunks/{index-ygm89806.js → index-ak40qeya.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,192 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ - **Payload label track** — a pulse label now appears at departure parked clear of the source box,
6
+ holds until the dot catches up, moves with the dot, parks clear of the destination box and stays
7
+ fully visible until the dot touches it, then fades out over 0.2 s after arrival (was: faded out
8
+ before arrival and near every box). Multi-hop routes hand off between hops with a short fade
9
+ instead of drawing over the intermediate node. One rule (`pulseLabelWindows` /
10
+ `pulseLabelAnchor`) drives playback, the animated SVG and the labelled flight timing, whose
11
+ legible share is now the share where the label follows the dot.
12
+ - **Readable payload labels in flight** — a pulse with a `label` now flies slower and at a steady
13
+ cruise (not `inOutCubic`), so its text stays legible while it travels: reading budget 0.6 s +
14
+ 0.05 s per character over the legible share of the flight, clamped to 1.2–3.2 s, never faster
15
+ than before. An explicit pulse `duration` still wins; unlabelled pulses are unchanged
16
+ (byte-identical output). Shared by playback, step moves, snapshots and the animated SVG.
17
+ - **Tones: colour carries meaning** — five tones (`note` blue · `warn` gold · `risk` rose · `good`
18
+ sage · `neutral` ink), one source shared with page tones (`theme/tones.ts`). Pulses take
19
+ `tone`, a payload `label` (mono, rides beside the dot, fades clear of nodes; layer gaps widen for
20
+ it), `land: "edge"` (the label stays as the arriving edge's label), `tint` (the target takes the
21
+ tone on arrival) and `stain` (each wire keeps the tone once the dot has passed). New `tone` step
22
+ field `{ ids, to, stagger? }` for nodes (all kinds), groups and edges: tinted face, toned
23
+ stroke / tag / detail, toned wire / ports / label, toned group border; 0.35 s crossfade; `to:
24
+ null` clears; `stagger` sweeps. `set` works on plain graph nodes (`label`, `detail`, `tag`, and a
25
+ detail-line `tone`), `status` on plain nodes draws the glyph at the start of the detail line;
26
+ boxes fit every version (no resize mid-story). `glow: { ids, tone }` glows in a tone. Caption
27
+ `*emphasis*` in the accent (`\*` literal). Timeline `tones`, frame `tone` / `litTone`, pulse
28
+ `tone` / `label`, caption `em`; HTML viewer, static SVG, snapshots, beat sheets and the
29
+ animated SVG (opacity-only tone layers) agree. Diagnostics with did-you-mean (tones, ids, pulse
30
+ fields), warnings for clipped labels, `tint` / `stain` without a tone, `land` without a label,
31
+ no-op tones, plain nodes still running at the end. Schema, docs ("Tones"), example
32
+ `examples/stories/request-colours.dataflow.json`. Specs without these features render
33
+ byte-identically.
34
+ - **Acts: problem → rewind → fix** — `story.acts` (2–6 `{ id, label, tone? }`) and step fields
35
+ `act` / `enter` (`rewind` default · `cut` · `continue`) tell several scenarios on one stage
36
+ (graph diagrams). Elements say `in: [acts]`, or `delta` decides (removed → first act, added →
37
+ later acts); act stories drop the delta look. Positions are shared; each edge is routed only
38
+ against the boxes it coexists with, so a first-act wire runs straight through the slot of a
39
+ node the fix inserts (lint ignores pairs that never coexist). A rewind plays the previous act
40
+ backwards (eased, `clamp(len / 4, 0.6, 1.5)` s, captions hidden; HTML glitch + blur, plain in
41
+ the animated SVG), a cut dips; then a morph retracts leaving wires, fades leaving boxes, reveals
42
+ entering ones and draws entering wires. Later acts start from the previous act's start state.
43
+ Act chip ("● label", "◀◀ rewind") on the HTML stage and in the animated SVG header. `#act=<id>`,
44
+ `#sheet=acts`, `snapshot --act / --sheet acts` (tool `act`, `sheet: "acts"`), `render --svg
45
+ --act`. Timeline `acts`, frame `act`. Diagnostics with did-you-mean. Examples
46
+ `examples/stories/secret-broker.architecture.json` and
47
+ `examples/stories/payment-retry.acts.architecture.json`.
48
+ - **Overlays: annotations, toasts, HUD** — `annotations: [{ id, on, side?, text, tone?, in? }]`:
49
+ one mono line on a node's left edge, above or below it, its room reserved by the layout (edge
50
+ labels avoid it); story `reveal` / `hide` / `show` / `type` (char or word, caret) / `set` (`text`,
51
+ `tone`) / `clear` / `tone` take annotation ids; a typed-first annotation is hidden until typed.
52
+ Step `toast` (`{ id?, near, title?, text, tone?, for? }`) and `dismiss` (ids or `"all"`):
53
+ floating cards placed deterministically at layout time (above the anchor, then shifted, then a
54
+ row higher, then below; never over nodes, groups, edge labels, annotations or toasts up at the
55
+ same time; the viewBox grows to fit), fade + rise + scale in, fade out; `tone` steps reach them;
56
+ warnings for toasts still up when an act or the story ends. `story.hud: [{ id, label, value?,
57
+ tone?, prefix?, suffix?, at? }]`: stage metrics driven by `counter` steps, toned and revealed
58
+ like elements; HTML stage overlay below the act chip, a header row in the animated SVG, a line on
59
+ beat / act sheet tiles. Rewinds and act resets cover all three. Timeline `toasts` / `hud`, frame
60
+ `toasts`; schema, docs ("Annotations", "Toasts", "HUD metrics"). The secret-broker and
61
+ payment-retry examples use them; `bun run gallery:stories` builds a local showcase
62
+ (`examples/stories/out/index.html`). Specs without these features render byte-identically.
63
+
64
+ ## 0.5.0
65
+
66
+ - **Scrollytelling and slides: viewer** — figure API `beats()` and `moveTo({ beat } | { t },
67
+ { animate })` (forward plays, 2.5× across two or more beats; backward rewinds like ←; reduced
68
+ motion / `animate: false` jump to the settled state with a pure follow camera). Scrolly
69
+ controller: `sp-scrolly-live`, the step crossing a 55 % trigger line is `.is-active` and moves
70
+ the figure; figure transport / gate / rail hidden in scrolly (zoom, Fit, Expand stay); cite hover
71
+ highlights and file cites open the drawer; `#scrolly=<id>&step=<n>` (settled, for snapshots).
72
+ Deck (present mode): `html.sp-presenting`, `.is-current` slide in a 1280×720 frame scaled to fit
73
+ (`--sp-slide-scale`), builds from page data (entry forward = start, backward = end), keys →
74
+ Space PgDn Enter / ← PgUp Backspace / Home End / O outline / ? help / Esc / P, Present button,
75
+ chrome (progress, `n / N`, prev / next), figure keys off while presenting, `#present=1|0`,
76
+ `#slide=n`, `#build=k`, `layout: "slides"` opens presenting; `slide-overflow` lint (content
77
+ needing a scale under 0.7). `__storyink.deck = { present, exit, next, prev, state }`. Snapshot
78
+ `--slides [--builds]`, `--scrolly <id|all>` (plugin `slides`, `builds`, `scrolly`): frames +
79
+ labelled sheets per theme, gates ready / lint / deterministic, `--preview`.
80
+ `scripts/deck-verify.ts` (`bun run verify:deck`).
81
+ - **Scrollytelling and slides (pages)**: `scrolly` block (a figure pinned beside prose step
82
+ cards; each step's `at` names a story step id, chapter stop, beat number, `start` or `end`;
83
+ `steps: "auto"` from narrated steps or stops; cites; decreasing targets warn), static / no-JS /
84
+ print fallback and live CSS (sticky graphic, `.is-active` steps, narrow stacking). Slides from
85
+ the same DOM: `layout: "slides"`, `present`, section `slide: { layout }`, `break` blocks, figure
86
+ `builds`; title slide + one slide per section, layouts `title` / `full` / `split` / `center` /
87
+ `flow` (auto inferred; `data-fig-shape="tall|wide"` gives tall figures the full slide height),
88
+ slide content CSS inside the viewer's 1280×720 frame, Present button. Figure `spec` paths may be
89
+ relative, absolute, `file://` or `~/`. Page data gains
90
+ `scrolly` and `slides` (targets `{ beat, t }` resolved from the figure timelines). Pages markup
91
+ gains `.sp-slide` wrappers (`display: contents` in the article) and `data-layout` /
92
+ `data-present`. Examples: `payment-retry.scrolly`, `storyink-0.4.0.scrolly`, `pr-review.deck`.
93
+ - **Pages** (`"type": "page"`): offline single-file explainer documents (PR / diff reviews, plan
94
+ reviews, recaps) rendered from JSON: header (eyebrow, title, subtitle, `change` meta, summary,
95
+ theme toggle), sticky contents sidebar with scroll-spy (auto at ≥ 4 sections; a top list on
96
+ narrow screens / without JS), sections of blocks: `prose` (safe Markdown subset, no raw HTML),
97
+ `figure` (an embedded storyink diagram, spec object or path, with a `claim` caption), `kpis`,
98
+ `table`, `cards`, `callout`, `filemap` (tree with status, +/− and bars; collapses past 16
99
+ files), `diff` (from `--changes` or hunk text), `code`, `risks`, `decisions`, `evidence`,
100
+ `timeline`, `checklist`, `details`, `columns`. Serif reading measure, wide figures / tables /
101
+ diffs, semantic tones on the delta palette (`pagePalettes` / `page` tokens), both themes, print
102
+ stylesheet. Core: `isPageSpec`, `validatePage` (figure diagnostics prefixed with the figure
103
+ path), `renderPageHtml`, `resolvePageChanges`, `layoutPage`; Node: `loadPage`, `writePage`.
104
+ CLI `render` / `validate` and the plugin's `storyink_render` / `storyink_validate` (new `path`
105
+ param on render) detect pages and resolve figure paths; `--svg` / `--animated-svg` on a page are
106
+ errors. Schema: root `type: "page"` alternative. The drawer's HTML diff is now a shared
107
+ `DiffView` (`src/core/render/DiffView.tsx`, same markup). Diagram `validate()` points pages at
108
+ `validatePage`. Examples in `examples/pages/`. Single-diagram output unchanged.
109
+ - **Pages: viewer** — page figures are the interactive viewer embedded (`renderFigure` →
110
+ server-rendered App in embedded mode): captions above an aspect-sized stage, compact toolbar (no
111
+ theme button, Expand), transport, gate, pan / zoom (wheel zoom needs Ctrl / ⌘ unless expanded so
112
+ the page scrolls), story playback, follow camera, narration rail beside the stage when the figure
113
+ is wide (container query) else below, drawer as a page overlay, Expand (fills the window; Esc
114
+ returns). The viewer bundle detects `#storyink-page-data` and hydrates every `[data-si-fig]` root:
115
+ keys act only on the focused figure (tabindex, click focuses), `#fig=<id>` scopes `t` / `camera`
116
+ / `motion` / `pace` / `drawer` / `rail` / `autoplay` / `static` to a figure (page-wide: `theme`,
117
+ `static=1`), unrelated hash changes (TOC anchors) don't refit, exports / counters / lint are
118
+ per figure, `__storyink = { page: true, figures: { id: api }, ready, whenReady, lint }`.
119
+ `#fig=<id>&solo=1` shows only that figure with the standalone chrome and contract. `idPrefix`
120
+ on `Diagram` / `renderSvg` (every def id and `url(#…)`), default empty. Snapshot detects page
121
+ HTML: full-page PNG per theme (width 1280; figures at their final frame), gates ready / lint
122
+ (figures + page text overflow) / deterministic, `--preview` split into ≤ 3 parts;
123
+ `--figure <id>` (tool param `figure`) runs the single-diagram pipeline on one figure.
124
+ `scripts/page-verify.ts` (`bun run verify:page`). Single-diagram output unchanged.
125
+ - **Change stories**: story step `change` (before look until the step: removed = plain, added =
126
+ absent, modified = plain; then removed edges retract and ghost, removed nodes strike and fade,
127
+ added ones reveal / draw on, modified ones flash and turn gold, diff nodes apply; badges, strikes
128
+ and legend items follow); `story: "changes"` / `{ steps: "changes" }` / `--story changes` /
129
+ tool `story: "changes"` derive the walkthrough (data-flow order, edges and attached diff nodes
130
+ per beat, hero pulse trains, overview, `narrate` from `summary`); `focus` takes a list (union
131
+ box); `spotlight: "veil"` (soft rounded cutout around the step's focus, gliding, gone at the
132
+ end; HTML and animated SVG). Animated SVG: diff rows settle exactly (tighter keyframe tolerance).
133
+ - **Narration rail and change drawer** (HTML viewer): story step `narrate: { heading?, body,
134
+ cites? }` (cites link body text to element ids, `node#row` or `path#L12-20`; validated in order,
135
+ refs must resolve). A narrated step without a caption captions with its heading (or the body's
136
+ first sentence) at compile time, so the animated SVG and beat tiles agree; narrated beats hold
137
+ for their body. The viewer shows a rail ("02 / 06", heading, body with cites) that tracks the
138
+ header caption's beat through playback, the scrubber, → / ← and the gate; side column on wide
139
+ screens, bottom sheet below 760 px; toolbar toggle and `N`. Clicking (not dragging) an element
140
+ with `summary` / `files` / a delta opens a drawer with its badge, stat, summary and file refs,
141
+ and with `--changes` the selected hunks as an HTML diff (gutter, tints, syntax colours,
142
+ intra-line marks); Esc / × close it. `#drawer=<id|path>`, `#rail=0|1`,
143
+ `__storyink.openDrawer(id)` / `closeDrawer()` / `drawer()`, `steps[i].narrate`. The scene carries
144
+ embedded `changes` for the drawer. `storyink snapshot --rail --drawer <id>` (tool params `rail`,
145
+ `drawer`; captures the 16:9 page viewport, gates compare the same configuration). The drawer
146
+ takes the rail's column while open. `scripts/narrate-verify.ts` (`bun run verify:narrate`). Example:
147
+ `examples/changes/storyink-0.4.0.pr.json` (commit b526d50). Specs without these fields render
148
+ byte-identically.
149
+ - **Diff code nodes**: `code` nodes take `diff` (unified hunk text, `{ file, lines?, context?,
150
+ max? }` resolved by `--changes`, or resolved `{ file, hunks }`): gutter with old | new numbers,
151
+ `+` / `−` markers, sage / rose row tints (`diffAddBg` / `diffDelBg` tokens) with stronger
152
+ intra-line marks (token-level LCS), muted `@@` hunk headers with their section, per-language
153
+ syntax colours, a `… N more lines` fold (`max`, default 24) and long lines cut at 72 columns.
154
+ Anchors `node#+14` (head line) / `node#-13` (base line); `line` takes them and `{ id, hunk }`.
155
+ Story step `apply` (`"id"` or `{ id, hunk?, cps? }`): the node shows the base version until
156
+ then, removed rows tint and strike, added rows open (rows below slide) and type in, gutter
157
+ numbers fade in; HTML and animated SVG. Validation for diff shapes (an unresolved `{ file }`
158
+ without `--changes` is an error with a hint), anchors, `apply` and set / clear / type on diff
159
+ nodes. `diffRows(hunks, lang, { max?, headers? })` row model in `storyink/core`. Animated SVG:
160
+ wired dashed edges and the labels of wired edges now fade with their draw (as in the HTML).
161
+ Change scenes: `unchanged` nodes use the neutral accent. Examples: `payment-retry`,
162
+ `etl-dedupe` (Python + SQL), `storyink-core` (rendered with `--changes`).
163
+ - **Change diagrams**: `delta` (`added` / `modified` / `removed` / `unchanged`) on nodes, groups,
164
+ edges, participants and messages; `emphasis` (`hero` / `muted`) on edges and messages; `stat`
165
+ (`+38 −12`), `summary` and `files` (paths or `{ path, lines, revision }`); root `change`
166
+ (`base → head` shown above the diagram) and `style.legend`. Added = sage + `NEW` badge,
167
+ modified = gold + `CHANGED`, removed = rose dashed ghost with a struck label + `REMOVED`,
168
+ unchanged = receded context; hero edges are thicker with a soft glow. Layout reserves badge room
169
+ (header strip, top strip, right slot or a centred row by shape) and a legend band listing the
170
+ deltas present. Static SVG, HTML and animated SVG (change scenes use the union SMIL mode; removed
171
+ edges draw on by opacity so they keep their dash); composes with story levels and rich nodes.
172
+ Validation with JSON paths and hints (enums, file refs, negative stats, added edges to removed
173
+ nodes, more than 2 hero edges). New palette `deltaPalettes` and `delta` geometry tokens, emitted
174
+ only when a scene uses them: specs without the new fields render byte-identically. Examples in
175
+ `examples/changes/` (`batch-email` ported from PR Lens, `auth-session-to-jwt` sequence,
176
+ `rate-limit-plugin` with panel / code / chip deltas).
177
+ - **Diff ingestion**: `storyink diff [<range>|<base> [<head>]] [--staged] [--patch file|-] [-o
178
+ changes.json] [--json] [-- <pathspec>…]` parses `git diff` (default: working tree vs the merge
179
+ base with main/master) into a `DiffSet` and prints a per-file summary; `render --changes
180
+ <changes.json|.diff|.patch>` resolves the spec first. Core (browser-safe): `parseUnifiedDiff`,
181
+ `parseHunks`, `selectHunks`, `statFor`, `langForPath`, `normalizeLang`, `coverage`,
182
+ `resolveChanges` (fills `stat` from `files`, resolves `diff: { file, lines }` code nodes,
183
+ embeds the referenced hunks as `changes`, capped at 400 lines per file, and warns about
184
+ uncovered files / hunks and refs that miss the diff). Node: `gitDiff`, `diffSetFrom`.
185
+ Plugin: `storyink_diff` tool and `changes` on `storyink_render`.
186
+ - **Code languages**: `py`, `go`, `rust`, `sql`, `yaml`, `sh` lexers; `lang` aliases (`python`,
187
+ `golang`, `rs`, `yml`, `bash`/`shell`/`zsh`, `typescript`/`tsx`, `javascript`/`jsx`) are
188
+ accepted and normalized. TS / JS / JSON colouring is unchanged.
189
+
3
190
  ## 0.4.0
4
191
 
5
192
  - **Rich nodes** (architecture, dataflow, workflow): `panel` (window with rows: `tag`, `icon`,