@conduction/docusaurus-preset 3.33.0 → 3.35.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/ANIMATION_ROADMAP.md +129 -0
- package/MISSING_COMPONENTS.md +15 -5
- package/package.json +3 -2
- package/src/components/AgentTrace/AgentTrace.jsx +26 -5
- package/src/components/AgentTrace/AgentTrace.module.css +41 -2
- package/src/components/AgentTrace/__tests__/AgentTrace.render.test.js +108 -0
- package/src/components/AppMock/AppMock.jsx +14 -5
- package/src/components/AppMock/AppMock.module.css +1702 -0
- package/src/components/AppMock/__tests__/AppMock.render.test.js +329 -0
- package/src/components/AppMock/variants/DeciDeskMock.jsx +46 -5
- package/src/components/AppMock/variants/DocuDeskMock.jsx +39 -9
- package/src/components/AppMock/variants/DoriathMock.jsx +28 -13
- package/src/components/AppMock/variants/HermiqMock.jsx +35 -15
- package/src/components/AppMock/variants/HrmqMock.jsx +46 -28
- package/src/components/AppMock/variants/LarpingAppMock.jsx +36 -7
- package/src/components/AppMock/variants/LaunchPadBiMock.jsx +12 -5
- package/src/components/AppMock/variants/LaunchPadTilesMock.jsx +6 -1
- package/src/components/AppMock/variants/LaunchPadWidgetsMock.jsx +17 -8
- package/src/components/AppMock/variants/NLDesignMock.jsx +15 -4
- package/src/components/AppMock/variants/OpenBuildMock.jsx +20 -5
- package/src/components/AppMock/variants/OpenCatalogiMock.jsx +23 -4
- package/src/components/AppMock/variants/OpenConnectorMock.jsx +25 -3
- package/src/components/AppMock/variants/OpenRegisterMock.jsx +13 -2
- package/src/components/AppMock/variants/PipelinQMock.jsx +12 -2
- package/src/components/AppMock/variants/PortaliqMock.jsx +21 -9
- package/src/components/AppMock/variants/ProcestMock.jsx +34 -7
- package/src/components/AppMock/variants/ScholiqMock.jsx +12 -6
- package/src/components/AppMock/variants/ShillinqMock.jsx +33 -17
- package/src/components/AppMock/variants/SoftwareCatalogMock.jsx +15 -5
- package/src/components/AppMock/variants/ZaakAfhandelAppMock.jsx +19 -4
- package/src/components/FlowMock/FlowMock.jsx +5 -3
- package/src/components/FlowMock/FlowMock.module.css +36 -9
- package/src/components/FlowMock/__tests__/FlowMock.render.test.js +83 -0
- package/src/components/LeafMock/LeafMock.jsx +23 -8
- package/src/components/LeafMock/LeafMock.module.css +123 -0
- package/src/components/LeafMock/__tests__/LeafMock.render.test.js +105 -0
- package/src/components/MockScene/MockScene.jsx +16 -2
- package/src/components/MockScene/MockScene.module.css +22 -2
- package/src/components/MockScene/__tests__/MockScene.render.test.js +101 -0
- package/src/components/SidebarMock/SidebarMock.jsx +29 -2
- package/src/components/SidebarMock/__tests__/SidebarMock.render.test.js +115 -0
- package/src/components/WidgetMock/WidgetMock.jsx +35 -14
- package/src/components/WidgetMock/__tests__/WidgetMock.render.test.js +125 -0
- package/src/components/WidgetMock/variants/DocuDeskAnonymise.jsx +6 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Animation roadmap — wave 6 (planned, not yet implemented)
|
|
2
|
+
|
|
3
|
+
Waves 1–5 of the animated-atomics programme are shipped (see
|
|
4
|
+
`MISSING_COMPONENTS.md` for the per-component coverage). This file
|
|
5
|
+
holds the designs for the two remaining L-size items from the
|
|
6
|
+
animation audit, plus the small registry debts the audit surfaced.
|
|
7
|
+
Nothing in this file is implemented; it is the working spec for the
|
|
8
|
+
next animation PR.
|
|
9
|
+
|
|
10
|
+
Rules carried over from the shipped waves, binding for wave 6 too:
|
|
11
|
+
|
|
12
|
+
- Tokens only; no images, no hardcoded colours.
|
|
13
|
+
- One KNVB-orange accent per component, maximum.
|
|
14
|
+
- Every loop is seamless (opacity-swap resets, no visible rewind).
|
|
15
|
+
- `running={false}` and `prefers-reduced-motion` render the
|
|
16
|
+
meaningful end state, not a blank.
|
|
17
|
+
- Timelines ride the `--am-dur`-style custom-property convention so
|
|
18
|
+
a consumer can retune duration without touching keyframes.
|
|
19
|
+
|
|
20
|
+
## 1 · Pipeline — the document travels the flow (L)
|
|
21
|
+
|
|
22
|
+
**Component:** `src/components/Pipeline/Pipeline.jsx` +
|
|
23
|
+
`Pipeline.module.css` (React, in the preset).
|
|
24
|
+
|
|
25
|
+
**Audit finding on the claimed dotted-line animation — VERIFIED, it
|
|
26
|
+
exists.** `Pipeline.module.css` draws the full-width dotted flow line
|
|
27
|
+
as `.steps::before` (a `repeating-linear-gradient` dash pattern) and
|
|
28
|
+
animates it with `flow-shift` (0.9s linear infinite,
|
|
29
|
+
`background-position 0 → 10px`, lines 39–67). The stacked mobile
|
|
30
|
+
layout (`max-width: 1100px`) switches to a vertical line with its own
|
|
31
|
+
`flow-shift-vert` twin, and `prefers-reduced-motion: reduce` disables
|
|
32
|
+
both. So the base "the line flows" motion is real and shipping; what
|
|
33
|
+
wave 6 adds is the *payload* making the journey, not the line itself.
|
|
34
|
+
|
|
35
|
+
**Design — a document hex rides the line:**
|
|
36
|
+
|
|
37
|
+
- A small terracotta document hex (pointy-top, ~10×11, the same
|
|
38
|
+
vocabulary as the AppMock `.doc` atoms) spawns at the left end-box
|
|
39
|
+
(the `IconList` "Sources" column), travels the dotted line
|
|
40
|
+
left-to-right, and pauses ~400ms at each step hex's equator.
|
|
41
|
+
- At each pause the step hex answers: a 1.06 scale pulse plus a brief
|
|
42
|
+
ring in the step's own family colour (mint for the OpenConnector
|
|
43
|
+
step, workspace-blue for the platform hex, terracotta for
|
|
44
|
+
OpenCatalogi — never a new orange). The pill inside the hex, when
|
|
45
|
+
present, bumps with it.
|
|
46
|
+
- Past the last step the document fades into the right end-box
|
|
47
|
+
("Consumers"), whose list items tick top-to-bottom with a 120ms
|
|
48
|
+
stagger (each item's icon tile flashes its border cobalt→mint→rest)
|
|
49
|
+
— the "everyone downstream can see it now" beat.
|
|
50
|
+
- Loop length: `--pl-dur`, default 9s. The traveller is hidden at
|
|
51
|
+
0%/100% (opacity swap), so the loop is seamless; the dotted line's
|
|
52
|
+
independent 0.9s `flow-shift` keeps running underneath and needs no
|
|
53
|
+
synchronisation.
|
|
54
|
+
- The vertical (stacked) layout re-routes the traveller top-to-bottom
|
|
55
|
+
along the vertical line; same beats.
|
|
56
|
+
- Static / reduced-motion: the traveller is hidden, every step hex at
|
|
57
|
+
rest, the consumer list fully ticked — the delivered end state.
|
|
58
|
+
(`flow-shift` is already disabled by the existing media query.)
|
|
59
|
+
- Implementation note: the traveller must be a sibling of `.steps`
|
|
60
|
+
positioned against the same `top: 85px` midline calc the line uses;
|
|
61
|
+
the hexes' `z-index: 1` will occlude it while it passes "through"
|
|
62
|
+
a hex, which is exactly the right reading.
|
|
63
|
+
|
|
64
|
+
## 2 · PlatformDiagram — the platform assembles (L, cross-repo)
|
|
65
|
+
|
|
66
|
+
**Component:** the preset's `PlatformDiagram/PlatformDiagram.jsx` is
|
|
67
|
+
a typed wrapper around the bespoke `<platform-diagram>` web component
|
|
68
|
+
that lives **in the design-system repo, not the preset**:
|
|
69
|
+
`preview/components/_lib/platform-diagram.js` +
|
|
70
|
+
`platform-diagram.css`. The animation therefore lands cross-repo: the
|
|
71
|
+
keyframes and progress logic go into `_lib/platform-diagram.{js,css}`
|
|
72
|
+
(runtime CSS is injected by the web component), while the preset
|
|
73
|
+
wrapper only grows a `running` prop it forwards as an attribute.
|
|
74
|
+
|
|
75
|
+
**Today:** the component already reveals on scroll via the
|
|
76
|
+
`--pd-list-progress` / `--pd-hex-progress` custom properties — a
|
|
77
|
+
one-shot entrance, not a loop.
|
|
78
|
+
|
|
79
|
+
**Design — assembly loop:**
|
|
80
|
+
|
|
81
|
+
- Beat 1, the workspace: the centre workspace slab draws in (scaleY
|
|
82
|
+
from the baseline) and its topbar dots pop left-to-right.
|
|
83
|
+
- Beat 2, the corner hexes: the four app hexes fly in from their
|
|
84
|
+
compass corners (translate + fade, 90ms stagger), each with a
|
|
85
|
+
1.05 settle-bounce as it docks.
|
|
86
|
+
- Beat 3, the flows: the connecting flow lines draw (stroke-dash
|
|
87
|
+
reveal) from workspace to each hex in the same order the hexes
|
|
88
|
+
landed.
|
|
89
|
+
- Beat 4, the lists: each side list's rows land with the shipped
|
|
90
|
+
`.sbLive`-style stagger; the workspace pill flashes mint once when
|
|
91
|
+
the last row is in — "the platform is complete".
|
|
92
|
+
- Hold ~2.5s assembled, then a soft opacity-cascade reset (the shipped
|
|
93
|
+
waves' convention; no rewind).
|
|
94
|
+
- Loop length: `--pd-dur`, default 12s. When a `running="false"`
|
|
95
|
+
attribute (or `prefers-reduced-motion`) is set, the diagram renders
|
|
96
|
+
fully assembled — identical to today's post-scroll-reveal state, so
|
|
97
|
+
the existing scroll-progress path can stay as the reduced-motion
|
|
98
|
+
fallback.
|
|
99
|
+
- Cross-repo sequencing: land the web-component change in
|
|
100
|
+
`preview/components/_lib/` first (the kit page
|
|
101
|
+
`preview/components/platform-overview.html` is the specimen), then
|
|
102
|
+
bump the copy the preset consumes, then add the wrapper prop +
|
|
103
|
+
render test in the preset. Two PRs, one per repo.
|
|
104
|
+
|
|
105
|
+
## 3 · Pointer — flow-canvas run animation (spec lives elsewhere)
|
|
106
|
+
|
|
107
|
+
The FlowMock atomic (shipped, wave 3) is the marketing abstraction of
|
|
108
|
+
the flow editor. The *product* counterpart — the run-line animation on
|
|
109
|
+
the real flow canvas — is specified in the **nextcloud-vue** repo
|
|
110
|
+
(spec merged; `CnFlowCanvas` run-animation spec). Do not re-specify it
|
|
111
|
+
here: when that lands, FlowMock should stay visually consistent with
|
|
112
|
+
it (mint run-line, ~600ms approval hold, single orange halo at the
|
|
113
|
+
approval node), but the preset carries no implementation work for it.
|
|
114
|
+
|
|
115
|
+
## 4 · SidebarMock icon-registry stand-ins (S, debt)
|
|
116
|
+
|
|
117
|
+
The audit flagged two tab icons that ship as stand-ins because the
|
|
118
|
+
`IntegrationIcon` registry has no dedicated glyph:
|
|
119
|
+
|
|
120
|
+
- `docudesk-signatures` — the Signatures tab renders the **mail**
|
|
121
|
+
icon. Needs a signature glyph (pen-stroke over a baseline, in the
|
|
122
|
+
registry's stroke style).
|
|
123
|
+
- `docudesk-pii-map` — the PII-map tab renders the **keycloak** icon.
|
|
124
|
+
Needs a PII/redaction glyph (masked-text bar or eye-off).
|
|
125
|
+
|
|
126
|
+
Add both to `IntegrationIcon/registry.js`, switch the two tab
|
|
127
|
+
definitions in `SidebarMock.jsx`, and extend the SidebarMock render
|
|
128
|
+
test to pin the new names. Until then the stand-ins are acceptable at
|
|
129
|
+
mock abstraction level but must not leak into product UI.
|
package/MISSING_COMPONENTS.md
CHANGED
|
@@ -56,12 +56,15 @@ Most consume one or more primitives.
|
|
|
56
56
|
- **GameModal** — `GameModal/GameModal.jsx`. End-of-game dialog with cross-game progress tracking. Subscribes to `connext:gameend` events.
|
|
57
57
|
- **CookieCli** — `CookieCli/CookieCli.jsx`. Terminal-styled cookie consent banner with localStorage persistence.
|
|
58
58
|
- **ComposeBlock** — `ComposeBlock/ComposeBlock.jsx`. Branded code block (cobalt-900 + Plex Mono + filename pill + copy button). For docker-compose, bash recipes, and any verbatim copy-paste content. Used on `/demo`.
|
|
59
|
-
- **AgentTrace** — `AgentTrace/AgentTrace.jsx`. Terminal-styled agent execution trace with cursor + mode chrome. Specimen: `preview/components/agent-trace.html`.
|
|
60
|
-
- **AppMock** — `AppMock/AppMock.jsx`. Selector that renders one of
|
|
61
|
-
- **
|
|
62
|
-
- **
|
|
59
|
+
- **AgentTrace** — `AgentTrace/AgentTrace.jsx`. Terminal-styled agent execution trace with cursor + mode chrome. Types itself out on a loop: lines reveal left-to-right on a 550ms stagger, status dots start after their line, ~2s hold, soft cascade restart; `running={false}` / `prefers-reduced-motion` show the full trace at rest. Specimen: `preview/components/agent-trace.html`.
|
|
60
|
+
- **AppMock** — `AppMock/AppMock.jsx`. Selector that renders one of 24 app-specific UI mockups (DeciDesk, DocuDesk, LarpingApp, LaunchPad + variants, NLDesign, PipelinQ, Portaliq, Procest, Scholiq, SoftwareCatalog, ZaakAfhandelApp, …; `openwoo` aliases to the OpenCatalogi mock). Each variant lives under `AppMock/variants/`. As of the wave-2/4 animation pass every product variant loops a one-scene story on the shared `--am-dur` timeline: portaliq form-submit, nldesign retheme, scholiq progress+credential, launchpad widgets/tiles/BI, pipelinq deal-close (wave 1+3); docudesk PII words blacking out + live dropzone, shillinq bank-line reconciliation, doriath unlock → countdown → relock, opencatalogi federated-search filter, openconnector record-travel + run insert, procest stage advance + row re-sort, openregister object landing + rail repaint (wave 2); decidesk vote tally + pending→adopted flip, hrmq timesheet submit→approve→payroll (content reworked from the earlier people-list, which misrepresented the app), larpingapp XP ripple, softwarecatalog CVE scan sweep, zaakafhandelapp submission travel, hermiq conversation + tool chips + approval tick (inline styles moved to the module), openbuild template→app ghost travel (wave 4). `running={false}` / `prefers-reduced-motion` freeze every loop to its meaningful end state. Specimen: `preview/components/app-mock.html`.
|
|
61
|
+
- **WidgetMock** — `WidgetMock/WidgetMock.jsx`. Standalone abstract of one Nextcloud dashboard widget (14 kinds: nine Conduction + five stock-Nextcloud framing widgets). The ten list-shaped kinds share a single wave-5 refresh primitive (`.wmLive` in AppMock.module.css): a fresh row slides in at the top, the bottom row clips out, and the frame's unread badge bumps — transform/opacity only, so tile height never jumps. `docudesk-anonymise` instead runs the `.zoneLive` dropzone loop (marching-ants dashes, a file hex drops, mint confirm flash). `running={false}` / `prefers-reduced-motion` freeze to the resting tile. Specimen: `preview/components/widget-mock.html`.
|
|
62
|
+
- **SidebarMock** — `SidebarMock/SidebarMock.jsx`. Token-painted Nextcloud right-side detail panel, one registered tab active (9 kinds across Procest, DocuDesk, OpenRegister, OpenCatalogi, OpenConnector, DeciDesk, stock activity). Standalone card or `embedded` into an AppMock's `.detail` slot. Wave-5 loop (`.sbLive`): the panel slides in from the right, the head wipes, the active-tab underline travels in from the neighbouring tab (positioned by `--sb-ix`/`--sb-n` custom properties set from the variant's active index), and the body rows land on a stagger. `running={false}` / `prefers-reduced-motion` freeze to the opened panel. Known stand-ins in the tab icon registry: Signatures tabs borrow the `mail` icon, PII-map tabs borrow `keycloak` (see `ANIMATION_ROADMAP.md`). Specimen: `preview/components/sidebar-mock.html`.
|
|
63
|
+
- **FlowMock** — `FlowMock/FlowMock.jsx`. Token-painted abstract of the flow editor: node cards on a dotted canvas with coloured left bars (mint trigger, lavender steps, vermillion end), curved grey edges with pill labels, and a mint run-line looping trigger → step → approval → end. The run holds ~600ms at the approval node while an orange halo flashes there (the component's one orange). Freezes to the full static route under `prefers-reduced-motion` or `running={false}`. Specimen: `preview/components/flow-mock.html`.
|
|
64
|
+
- **LeafMock** — `LeafMock/LeafMock.jsx`. Small integration-leaf panels for Showcase items: `leaf="contacts"` (hex-avatar rows + mint sync arrow to a phone), `"calendar"` (mini month grid + linked-event chip), `"mail"` (message list + mint create-from-email chip), `"files"` (folder/file rows + forest link-to-record chip). Each leaf animates on the shared 6s `--lm-dur` loop (sync pulse + phone rows; day pop + chip slide; row highlight + chip pop; row + link-icon draw); `running={false}` / `prefers-reduced-motion` freeze to the end state.
|
|
63
65
|
- **BuildMock** — `BuildMock/BuildMock.jsx`. Looping "make it yours" animation: the orange OpenBuild edit FAB is pressed, a component palette opens, and a widget is dragged into the page layout where it snaps in (~8s loop). `prefers-reduced-motion` / `running={false}` renders the static end state (component placed).
|
|
64
66
|
- **KanbanMock** — `KanbanMock/KanbanMock.jsx`. Looping kanban drag for the pipelinq hero: a cursor lifts the top lead card, drags it to the next column, drops it flush into the column's card slot, and at the drop the column counts nudge (3→2, 2→3) and the per-lane € KPI tiles above the board shift the moved deal's value (~7s loop, clean reset). `prefers-reduced-motion` / `running={false}` renders the static board with the initial counts and totals.
|
|
67
|
+
- **MockScene** — `MockScene/MockScene.jsx`. Free-form positioning stage for overlap shots of WidgetMocks + SidebarMocks (caller supplies canvas size and per-item x/y/z). The scene assembles on a loop: items land on a 220ms stagger, transform-only so the inline left/top positioning is never fought; `running={false}` / `prefers-reduced-motion` show the assembled scene.
|
|
65
68
|
- **ExternalAppShelf** — `ExternalAppShelf/ExternalAppShelf.jsx`. Third-party-app shelf (Outlook-style, Mattermost, Keycloak, etc.) used on `/connext`. Specimen: `preview/components/external-app-shelf.html`.
|
|
66
69
|
- **WidgetShelf** — `WidgetShelf/WidgetShelf.jsx`. Widget showcase with eyebrow + title + lede. Default is a slow single-row carousel (seamless duplicated track, pause on hover/focus, static wrapping grid under `prefers-reduced-motion`); loop duration derives from the widget count (6s per card, min 30s; `speed` overrides), with pause/play + prev/next controls (cobalt ghost, hidden under reduced motion). Widgets without an explicit `panel` get a deterministic auto-generated mini panel (KPI tile / bar chart / list rows / donut, hashed from the title). `carousel={false}` renders the N-column grid. Specimen: `preview/components/widget-shelf.html`.
|
|
67
70
|
- **Showcase** — `Showcase/Showcase.jsx`. Multi-item collapsible showcase with screenshot + body. Used on `/commonground` for the "5-lagen model". Specimen: `preview/components/showcase.html`.
|
|
@@ -158,4 +161,11 @@ plus the new `IntegrationsBlock`); the locale-switcher integration on
|
|
|
158
161
|
the `PartnerDirectory solutions` field (consumed in the offering facet);
|
|
159
162
|
the `PartnerCard logoAlt` prop (accepted, with `name + ' logo'` fallback).
|
|
160
163
|
|
|
161
|
-
No open gaps as of 2026-05-19.
|
|
164
|
+
No open component gaps as of 2026-05-19.
|
|
165
|
+
|
|
166
|
+
Animation coverage (2026-08-19): waves 1–5 of the animated-atomics
|
|
167
|
+
programme shipped — every AppMock product variant, the WidgetMock
|
|
168
|
+
list-refresh + dropzone primitives, and the SidebarMock open loop.
|
|
169
|
+
The two remaining L-size items (Pipeline document-travels-the-flow,
|
|
170
|
+
PlatformDiagram assembly) and the SidebarMock icon-registry stand-ins
|
|
171
|
+
are specified in [`ANIMATION_ROADMAP.md`](ANIMATION_ROADMAP.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/docusaurus-preset",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.35.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"prepack": "node scripts/prepack-bundle-css.js",
|
|
6
6
|
"test": "node --test"
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"static/",
|
|
35
35
|
"bin/",
|
|
36
36
|
"README.md",
|
|
37
|
-
"MISSING_COMPONENTS.md"
|
|
37
|
+
"MISSING_COMPONENTS.md",
|
|
38
|
+
"ANIMATION_ROADMAP.md"
|
|
38
39
|
],
|
|
39
40
|
"keywords": [
|
|
40
41
|
"conduction",
|
|
@@ -47,6 +47,14 @@
|
|
|
47
47
|
* - kind 'status' → KNVB-orange "ing…" status line
|
|
48
48
|
* - cursor: boolean — render a blinking cursor at the bottom
|
|
49
49
|
* - mode: string — bottom strip caption ("accept edits on…")
|
|
50
|
+
* - running: boolean (default true) — the trace types itself out:
|
|
51
|
+
* each line reveals left-to-right on a 550ms stagger (a per-line
|
|
52
|
+
* --at-d delay; the loop period --at-total derives from the line
|
|
53
|
+
* count plus a ~2s hold), status-line dots start ticking only
|
|
54
|
+
* after their line has appeared, and the loop restarts softly by
|
|
55
|
+
* fading lines out in the same order they arrived. `running={false}`
|
|
56
|
+
* and prefers-reduced-motion show the full trace at rest (every
|
|
57
|
+
* line visible — the meaningful end state).
|
|
50
58
|
*/
|
|
51
59
|
|
|
52
60
|
import React from 'react';
|
|
@@ -62,12 +70,25 @@ function bulletClass(b) {
|
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
export default function AgentTrace({lines = [], cursor = false, mode, className}) {
|
|
73
|
+
export default function AgentTrace({lines = [], cursor = false, mode, running = true, className}) {
|
|
74
|
+
/* Typed-reveal timing: line i appears at i × 550ms; after the last
|
|
75
|
+
line a ~2s hold, then a 600ms fade window before the loop. The
|
|
76
|
+
CSS animation reads both custom properties. */
|
|
77
|
+
const STEP = 550;
|
|
78
|
+
const total = lines.length * STEP + 2600;
|
|
79
|
+
const delayOf = (i) => ({'--at-d': `${i * STEP}ms`});
|
|
66
80
|
return (
|
|
67
|
-
<div
|
|
68
|
-
|
|
81
|
+
<div
|
|
82
|
+
className={[styles.trace, !running && styles.static, className].filter(Boolean).join(' ')}
|
|
83
|
+
role="img"
|
|
84
|
+
aria-label="Agent trace"
|
|
85
|
+
>
|
|
86
|
+
<div className={styles.body} style={{'--at-total': `${total}ms`}}>
|
|
69
87
|
{lines.map((ln, i) => {
|
|
70
|
-
const indent =
|
|
88
|
+
const indent = {
|
|
89
|
+
...(ln.indent ? {paddingLeft: `${ln.indent * 18}px`} : null),
|
|
90
|
+
...delayOf(i),
|
|
91
|
+
};
|
|
71
92
|
if (ln.kind === 'expand') {
|
|
72
93
|
return (
|
|
73
94
|
<div key={i} className={[styles.line, styles.expand].join(' ')} style={indent}>
|
|
@@ -111,7 +132,7 @@ export default function AgentTrace({lines = [], cursor = false, mode, className}
|
|
|
111
132
|
);
|
|
112
133
|
})}
|
|
113
134
|
{cursor && (
|
|
114
|
-
<div className={[styles.line, styles.cursorLine].join(' ')}>
|
|
135
|
+
<div className={[styles.line, styles.cursorLine].join(' ')} style={delayOf(lines.length)}>
|
|
115
136
|
<span className={styles.prompt}>></span>
|
|
116
137
|
<span className={styles.cursor} aria-hidden="true"></span>
|
|
117
138
|
</div>
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* <AgentTrace /> styles. Cobalt-900 terminal panel with monospace
|
|
3
3
|
* lines and Claude-CLI-style coloured bullets.
|
|
4
|
+
*
|
|
5
|
+
* Typed reveal: every .line runs one shared looping animation whose
|
|
6
|
+
* period is --at-total (set inline by the component from the line
|
|
7
|
+
* count) and whose start is staggered by the per-line --at-d delay
|
|
8
|
+
* (i × 550ms, also inline). A line clips open left-to-right over the
|
|
9
|
+
* first ~4% of the period, holds, and fades near the end — with the
|
|
10
|
+
* stagger, lines type in and later fade out in arrival order, so the
|
|
11
|
+
* restart is a soft cascade rather than a hard cut. Status dots and
|
|
12
|
+
* the prompt cursor inherit the same delay so they only start after
|
|
13
|
+
* their line exists. `.static` (running={false}) and
|
|
14
|
+
* prefers-reduced-motion show every line at rest.
|
|
4
15
|
*/
|
|
5
16
|
|
|
6
17
|
.trace {
|
|
@@ -29,6 +40,14 @@
|
|
|
29
40
|
align-items: flex-start;
|
|
30
41
|
gap: var(--space-2);
|
|
31
42
|
font-feature-settings: "calt" 0;
|
|
43
|
+
animation: atType var(--at-total, 6s) linear var(--at-d, 0ms) infinite both;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@keyframes atType {
|
|
47
|
+
0% { opacity: 0; clip-path: inset(0 100% 0 0); }
|
|
48
|
+
4% { opacity: 1; clip-path: inset(0 0 0 0); }
|
|
49
|
+
90% { opacity: 1; clip-path: inset(0 0 0 0); }
|
|
50
|
+
96%, 100% { opacity: 0; clip-path: inset(0 0 0 0); }
|
|
32
51
|
}
|
|
33
52
|
|
|
34
53
|
/* Bullets — set inline-block so the gap works the same across kinds. */
|
|
@@ -56,7 +75,9 @@
|
|
|
56
75
|
.dots {
|
|
57
76
|
display: inline-block;
|
|
58
77
|
margin-left: 2px;
|
|
59
|
-
|
|
78
|
+
/* Inherits the line's --at-d so the dots only start ticking after
|
|
79
|
+
their line has typed in; backwards-fill hides them until then. */
|
|
80
|
+
animation: dots 1400ms steps(4, end) var(--at-d, 0ms) infinite backwards;
|
|
60
81
|
white-space: pre;
|
|
61
82
|
overflow: hidden;
|
|
62
83
|
vertical-align: bottom;
|
|
@@ -94,7 +115,8 @@
|
|
|
94
115
|
height: 14px;
|
|
95
116
|
background: white;
|
|
96
117
|
vertical-align: -2px;
|
|
97
|
-
|
|
118
|
+
/* Same delay-inheritance as .dots: blinking starts with its line. */
|
|
119
|
+
animation: blink 1100ms steps(2, end) var(--at-d, 0ms) infinite backwards;
|
|
98
120
|
}
|
|
99
121
|
@keyframes blink {
|
|
100
122
|
0%, 50% { opacity: 1; }
|
|
@@ -113,3 +135,20 @@
|
|
|
113
135
|
}
|
|
114
136
|
.modeArrow { color: var(--c-cobalt-300); }
|
|
115
137
|
.modeText { color: var(--c-cobalt-200); }
|
|
138
|
+
|
|
139
|
+
/* ---- Static trace (running={false} and reduced motion) ----
|
|
140
|
+
Every line visible at rest, dots at full width, cursor solid. */
|
|
141
|
+
|
|
142
|
+
.static .line,
|
|
143
|
+
.static .dots,
|
|
144
|
+
.static .cursor {
|
|
145
|
+
animation: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@media (prefers-reduced-motion: reduce) {
|
|
149
|
+
.line,
|
|
150
|
+
.dots,
|
|
151
|
+
.cursor {
|
|
152
|
+
animation: none;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentTrace.render.test.js — SSR-renders the real <AgentTrace> and
|
|
3
|
+
* asserts the typed-reveal wiring: per-line --at-d delays on the
|
|
4
|
+
* 550ms stagger, the loop period --at-total derived from the line
|
|
5
|
+
* count, the cursor delayed until after the last line, and the
|
|
6
|
+
* `running` prop's static class. Same esbuild harness as
|
|
7
|
+
* KanbanMock.render.test.js; CSS modules stubbed to identity.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const test = require('node:test');
|
|
13
|
+
const {before, after} = test;
|
|
14
|
+
const assert = require('node:assert/strict');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const fs = require('node:fs/promises');
|
|
17
|
+
const {build} = require('esbuild');
|
|
18
|
+
const React = require('react');
|
|
19
|
+
const {renderToStaticMarkup} = require('react-dom/server');
|
|
20
|
+
|
|
21
|
+
const COMPONENT = path.resolve(__dirname, '..', 'AgentTrace.jsx');
|
|
22
|
+
const PRESET_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
|
|
23
|
+
const SCRATCH = path.join(PRESET_ROOT, '.tmp-agent-trace-test');
|
|
24
|
+
|
|
25
|
+
const LINES = [
|
|
26
|
+
{kind: 'note', bullet: 'done', text: 'Found the root cause.'},
|
|
27
|
+
{kind: 'tool', text: 'openconnector - run_pipeline (MCP)'},
|
|
28
|
+
{kind: 'expand', text: '+38 lines (ctrl+o to expand)'},
|
|
29
|
+
{kind: 'continuation', text: 'register_slug: "zaak", limit: 50'},
|
|
30
|
+
{kind: 'status', text: 'Synthesising…'},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const cssModuleStub = {
|
|
34
|
+
name: 'css-module-stub',
|
|
35
|
+
setup(b) {
|
|
36
|
+
b.onResolve({filter: /\.module\.css$/}, (args) => ({path: args.path, namespace: 'css-stub'}));
|
|
37
|
+
b.onLoad({filter: /.*/, namespace: 'css-stub'}, () => ({
|
|
38
|
+
contents: 'export default new Proxy({}, {get: (_, p) => p});',
|
|
39
|
+
loader: 'js',
|
|
40
|
+
}));
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let AgentTrace;
|
|
45
|
+
|
|
46
|
+
before(async () => {
|
|
47
|
+
await fs.mkdir(SCRATCH, {recursive: true});
|
|
48
|
+
const tmpDir = await fs.mkdtemp(path.join(SCRATCH, 'run-'));
|
|
49
|
+
const outFile = path.join(tmpDir, 'bundle.cjs');
|
|
50
|
+
await build({
|
|
51
|
+
entryPoints: [COMPONENT],
|
|
52
|
+
outfile: outFile,
|
|
53
|
+
bundle: true,
|
|
54
|
+
format: 'cjs',
|
|
55
|
+
jsx: 'automatic',
|
|
56
|
+
jsxImportSource: 'react',
|
|
57
|
+
tsconfigRaw: {compilerOptions: {jsx: 'react-jsx', jsxImportSource: 'react'}},
|
|
58
|
+
platform: 'node',
|
|
59
|
+
external: ['react'],
|
|
60
|
+
plugins: [cssModuleStub],
|
|
61
|
+
logLevel: 'warning',
|
|
62
|
+
});
|
|
63
|
+
delete require.cache[require.resolve(outFile)];
|
|
64
|
+
AgentTrace = require(outFile).default;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
after(async () => {
|
|
68
|
+
await fs.rm(SCRATCH, {recursive: true, force: true});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
function render(props) {
|
|
72
|
+
return renderToStaticMarkup(React.createElement(AgentTrace, props));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
test('SSR smoke: renders every line kind', () => {
|
|
76
|
+
const html = render({lines: LINES, cursor: true, mode: 'accept edits on'});
|
|
77
|
+
for (const cls of ['note', 'tool', 'expand', 'continuation', 'status', 'cursorLine', 'modeBar']) {
|
|
78
|
+
assert.match(html, new RegExp(`class="[^"]*\\b${cls}\\b`), `missing ${cls}`);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('typed reveal: each line carries its --at-d delay on the 550ms stagger', () => {
|
|
83
|
+
const html = render({lines: LINES});
|
|
84
|
+
for (let i = 0; i < LINES.length; i++) {
|
|
85
|
+
assert.match(html, new RegExp(`--at-d:${i * 550}ms`), `line ${i}: missing delay`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('typed reveal: the loop period --at-total derives from the line count (+ hold)', () => {
|
|
90
|
+
const html = render({lines: LINES});
|
|
91
|
+
assert.match(html, new RegExp(`--at-total:${LINES.length * 550 + 2600}ms`));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('cursor: delayed until after the last line', () => {
|
|
95
|
+
const html = render({lines: LINES, cursor: true});
|
|
96
|
+
assert.match(html, new RegExp(`--at-d:${LINES.length * 550}ms`));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('indented lines keep their padding alongside the delay', () => {
|
|
100
|
+
const html = render({lines: [{kind: 'note', text: 'x', indent: 2}]});
|
|
101
|
+
assert.match(html, /padding-left:36px/);
|
|
102
|
+
assert.match(html, /--at-d:0ms/);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('P0 plumbing: running={false} adds the static class to the trace root; default omits it', () => {
|
|
106
|
+
assert.match(render({lines: LINES, running: false}), /class="trace static"/);
|
|
107
|
+
assert.doesNotMatch(render({lines: LINES}), /\bstatic\b/);
|
|
108
|
+
});
|
|
@@ -22,6 +22,12 @@
|
|
|
22
22
|
* Props:
|
|
23
23
|
* - app: keyof VARIANTS (required)
|
|
24
24
|
* - size: 'sm' | 'md' (default) — frame width
|
|
25
|
+
* - running: boolean (default true) — false freezes every variant
|
|
26
|
+
* animation to its meaningful static end state (the same
|
|
27
|
+
* rendering `prefers-reduced-motion: reduce` gives).
|
|
28
|
+
* Forwarded to the variant component as well, for variants
|
|
29
|
+
* that need JS-side awareness; the CSS `.static` class on
|
|
30
|
+
* the frame is what actually stops the keyframes.
|
|
25
31
|
* - sidebar: ReactNode — renders inside
|
|
26
32
|
* the right-edge overlay of the frame. Pass a SidebarMock
|
|
27
33
|
* JSX to model a Nextcloud sidebar opened over this app
|
|
@@ -47,7 +53,6 @@ import DeciDeskMock from './variants/DeciDeskMock.jsx';
|
|
|
47
53
|
import DocuDeskMock from './variants/DocuDeskMock.jsx';
|
|
48
54
|
import LarpingAppMock from './variants/LarpingAppMock.jsx';
|
|
49
55
|
import NLDesignMock from './variants/NLDesignMock.jsx';
|
|
50
|
-
import OpenWooMock from './variants/OpenWooMock.jsx';
|
|
51
56
|
import PipelinQMock from './variants/PipelinQMock.jsx';
|
|
52
57
|
import SoftwareCatalogMock from './variants/SoftwareCatalogMock.jsx';
|
|
53
58
|
import ZaakAfhandelAppMock from './variants/ZaakAfhandelAppMock.jsx';
|
|
@@ -74,7 +79,11 @@ const VARIANTS = {
|
|
|
74
79
|
docudesk: {Component: DocuDeskMock, label: 'DocuDesk'},
|
|
75
80
|
larpingapp: {Component: LarpingAppMock, label: 'LarpingApp'},
|
|
76
81
|
nldesign: {Component: NLDesignMock, label: 'NLDesign'},
|
|
77
|
-
openwoo
|
|
82
|
+
/* `openwoo` is an alias: the OpenWoo app is retired in favour of
|
|
83
|
+
OpenCatalogi, so the slug renders the OpenCatalogi mock. The
|
|
84
|
+
OpenWooMock.jsx variant file is kept for now (no importer left);
|
|
85
|
+
delete it in a later cleanup wave. */
|
|
86
|
+
openwoo: {Component: OpenCatalogiMock, label: 'OpenCatalogi'},
|
|
78
87
|
pipelinq: {Component: PipelinQMock, label: 'PipelinQ'},
|
|
79
88
|
softwarecatalog: {Component: SoftwareCatalogMock, label: 'SoftwareCatalog'},
|
|
80
89
|
zaakafhandelapp: {Component: ZaakAfhandelAppMock, label: 'ZaakAfhandelApp'},
|
|
@@ -88,7 +97,7 @@ const VARIANTS = {
|
|
|
88
97
|
'app-versions': {Component: AppVersionsMock, label: 'App Versions'},
|
|
89
98
|
};
|
|
90
99
|
|
|
91
|
-
export default function AppMock({app, size = 'md', sidebar = null, caption = false, className}) {
|
|
100
|
+
export default function AppMock({app, size = 'md', sidebar = null, caption = false, running = true, className}) {
|
|
92
101
|
const variant = VARIANTS[app];
|
|
93
102
|
if (!variant) {
|
|
94
103
|
return (
|
|
@@ -113,8 +122,8 @@ export default function AppMock({app, size = 'md', sidebar = null, caption = fal
|
|
|
113
122
|
return (
|
|
114
123
|
<div className={styles.am}>
|
|
115
124
|
<figure className={[styles.figure, className].filter(Boolean).join(' ')}>
|
|
116
|
-
<div className={[styles.frame, styles[`size-${size}`]].filter(Boolean).join(' ')}>
|
|
117
|
-
<Component sidebar={renderedSidebar} />
|
|
125
|
+
<div className={[styles.frame, styles[`size-${size}`], !running && styles.static].filter(Boolean).join(' ')}>
|
|
126
|
+
<Component sidebar={renderedSidebar} running={running} />
|
|
118
127
|
</div>
|
|
119
128
|
{caption && <figcaption className={styles.caption}>{label}</figcaption>}
|
|
120
129
|
</figure>
|