diff --git a/.changeset/explorer-on-event.md b/.changeset/explorer-on-event.md new file mode 100644 index 000000000..c6e5cc74d --- /dev/null +++ b/.changeset/explorer-on-event.md @@ -0,0 +1,11 @@ +--- +'@walkeros/explorer': patch +--- + +Adds the `on-event` design token: text on an event-colour fill, such as a tag +card's Done button. Dark ink on the dark event colours, white on the light ones, +both above 4.5:1. + +Adds `EventLog` to the design components: the walkerOS events of a session, +newest first, beside the selected event as fields or as JSON, with copy and +clear. diff --git a/.changeset/frame-links.md b/.changeset/frame-links.md new file mode 100644 index 000000000..b03f8ddad --- /dev/null +++ b/.changeset/frame-links.md @@ -0,0 +1,11 @@ +--- +'@walkeros/mcp': minor +'@walkeros/cli': patch +--- + +`frame_manage` returns the frames a frame shows as `links` and its variant group +as `variantSetId`. `parentId`, `extends` and `placements` are gone from its +output, from the exported frame types and from what the CLI's `listFrames`, +`listPageFrames` and `getFrame` return. A page read returns that page's frames +only. `FRAME_HINT_EXTENDS_BASE` is now `FRAME_HINT_FOLLOW_LINKS`. Needs the +walkerOS app release that serves links. diff --git a/.changeset/tag-mode-loader.md b/.changeset/tag-mode-loader.md new file mode 100644 index 000000000..46281350d --- /dev/null +++ b/.changeset/tag-mode-loader.md @@ -0,0 +1,11 @@ +--- +'@walkeros/core': patch +'@walkeros/cli': patch +'@walkeros/walker.js': patch +--- + +walker.js and every web bundle the CLI builds now carry the Tag Mode loader, +`moin()` from `@walkeros/core`. It stays idle unless the walkerOS app opens your +page for Tag Mode (`?elbMoin`), then loads the Tag Mode script from +`cdn.walkeros.io`. Without that host in your CSP's `script-src`, it loads +nothing (a `'strict-dynamic'` policy admits it through walker.js's nonce). diff --git a/apps/explorer/AGENT.md b/apps/explorer/AGENT.md index b3026e582..fb1a1fe34 100644 --- a/apps/explorer/AGENT.md +++ b/apps/explorer/AGENT.md @@ -160,11 +160,16 @@ design tokens; walkeros.io's home page is built from it. - Folders follow the atomic order: `atoms/` (Button, InlineCode, EventLegend, Icon, InstallCommand, Card, Eyebrow, Stat, Text, TextLink, PhotoPlaceholder), `molecules/` (CheckList, SectionHeading, ProblemCard, FeatureItem, FaqItem, - PlanCard, CaseCard, HighlightCard, BrowserFrame, QuickstartSteps), `layout/` - (Section, CardGrid, Split, Cluster (`separated` sets a call to action apart - from the content above), Hero) and `viz/` (HeroTaggingViz, + PlanCard, CaseCard, HighlightCard, BrowserFrame, EventLog, QuickstartSteps), + `layout/` (Section, CardGrid, Split, Cluster (`separated` sets a call to + action apart from the content above), Hero) and `viz/` (HeroTaggingViz, ArticleTeaserTracking, DestinationMappingViz). Nothing imports upward; a demo may use atoms. +- Reuse before a new style: a molecule's buttons are the `Button` atom, its code + chips `InlineCode`, its glyphs `Icon`. `atoms/` also keeps two pieces outside + the entry that more than one folder uses: `CodeTokens` (code runs in the + demos' colours, for a dark ground; the token type is `codeTokens.ts`) and + `useCopy` (copy to the clipboard, then say how it went). - Each folder keeps its own `index.ts`; the entry `index.ts` re-exports the four, so names stay flat. Everything the entry reaches imports only `react` (`__tests__/entry.test.ts` fails otherwise), and the demo data in `viz/data/` diff --git a/apps/explorer/STYLE.md b/apps/explorer/STYLE.md index 67c83e879..940e2ef09 100644 --- a/apps/explorer/STYLE.md +++ b/apps/explorer/STYLE.md @@ -50,9 +50,9 @@ never a component. | Text | `--fg` (primary), `--fg-2` (secondary, labels, inactive buttons), `--fg-3` (meta, placeholder, disabled) | | Grounds | `--bg` (page, inputs), `--bg-2` (headers, footers, button groups), `--surface` (boxes, menus), `--surface-2` (hover, inline code) | | Edges | `--border` (hairlines), `--border-strong` (inputs, controls, floating layers), `--focus` (focus ring) | -| Actions | `--primary` fill with `--on-primary` text, `--link` for text in the brand colour, `--danger` with `--on-danger` | +| Actions | `--primary` with `--on-primary`, `--link` for text in the brand colour, `--danger` with `--on-danger`, a tag card's Done with `--on-event` | | Status | `--success`, `--warning`, `--danger`, `--info`, tints `--success-bg` and the like; always with an icon and a word | -| Events | `--event-entity`, `--event-action`, `--event-property`, `--event-context`, `--event-globals`; on dark grounds only | +| Events | `--event-entity`, `--event-action`, `--event-property`, `--event-context`, `--event-globals`; dark grounds, and a tag card's Done | | Flow steps | `--step-source`, `--step-transformer`, `--step-collector`, `--step-destination`, `--step-store`, `--platform-web`, `--platform-server` | | Code | `--code-bg`, `--code-bar`, `--code-border`, `--code-fg`; editor colours are the `syntax-*` constants | | Product visuals | `--viz-*` | @@ -640,7 +640,9 @@ its own root. grounds; control edges, the focus ring and step colours 3:1. - `--primary` is a fill under `--on-primary`; text in the brand colour is `--link`. -- Event colours sit only on dark grounds (a dark island). +- Event colours sit only on dark grounds (a dark island). One exception: a tag + card's Done is filled with its kind colour and takes `--on-event` for its + text; the colour names the kind, the word names the action. - Every status shows with an icon and a word, never by colour alone. Any other state a colour tells also gets a glyph, an edge style or a shape, and its word (visible, or in the tooltip and the accessible name where it does not fit). diff --git a/apps/explorer/design/README.md b/apps/explorer/design/README.md index 64d49a64e..50db62909 100644 --- a/apps/explorer/design/README.md +++ b/apps/explorer/design/README.md @@ -65,7 +65,7 @@ The app, the explorer and Tag Mode use these tokens with a few more rules. - **Status.** `danger`, `success`, `warning` and `info` always come with an icon and a word. On a tinted `*-bg` fill the text is `fg`; the status colour marks the icon and the border. `success` sits on the blue side of green so it stays apart from `danger` with red-green colour blindness. Any other state a hue tells (seen, not seen yet, added, removed) also gets a cue without colour, an edge style, a glyph or a shape, and its word: visible where it fits, otherwise in the tooltip and the accessible name. - **Flow steps.** `step-source`, `step-transformer`, `step-collector`, `step-destination` and `step-store` colour step badges, node accents and edges; `platform-web` and `platform-server` mark platforms. Always beside the word. - **Charts and notes.** Series take `chart-1` to `chart-6` in order, each labelled. `annotation` marks human notes and comment pins. -- **Tag kinds.** The five event parts have a dark value for dark grounds and a light value that reads on the light theme's page grounds, so a key on light chrome (a tool button's word) can take its event colour directly. Mark layers, legends, code and visualisations stay dark islands and show the dark values in both themes. `event-user` and `event-consent` have the dark value only: dark grounds only. Every event colour always comes with its word. +- **Tag kinds.** The five event parts have a dark value for dark grounds and a light value that reads on the light theme's page grounds, so a key on light chrome (a tool button's word) can take its event colour directly. Mark layers, legends, code and visualisations stay dark islands and show the dark values in both themes. `event-user` and `event-consent` have the dark value only: dark grounds only. Every event colour always comes with its word. One exception to never using these hues for UI states: a tag card's one action, Done, is filled with its kind colour and takes `on-event` for its text. The colour names the kind; the word names the action. - **Editors.** Monaco, Shiki and Prism use `syntax-*` on `code-bg`, with `code-line-number`, `code-selection`, `code-inserted-bg` and `code-deleted-bg`. Visualisations keep `viz-*`. Code surfaces stay dark in both themes. - **Tailwind.** `tokens.css` declares `--radius-*`, `--font-sans` and `--font-mono` under Tailwind's own names, so importing it alone already re-scales Tailwind's `rounded-*` (`rounded-lg` becomes 12px) and re-fonts `font-sans` and `font-mono`. Move radius classes to the design scale in the same change (`rounded-lg` to `rounded-md`, `rounded-xl` to `rounded-lg`). `tailwind.css` then removes the palette, the default radii, sizes and shadows, and maps `max-w-*` to the `container-*` widths. diff --git a/apps/explorer/design/tokens.json b/apps/explorer/design/tokens.json index cd05ad15e..4f292ce30 100644 --- a/apps/explorer/design/tokens.json +++ b/apps/explorer/design/tokens.json @@ -319,6 +319,14 @@ }, "usage": "Text and icons on a `danger` fill such as a destructive button (dark 8.6:1, light 6.5:1)." }, + { + "name": "on-event", + "value": { + "dark": "#111827", + "light": "#ffffff" + }, + "usage": "Text and icons on an event-colour fill: a tag card's Done. Dark ink on the dark event values (5.6:1 or more), white on the light ones (about 5:1)." + }, { "name": "success", "value": { diff --git a/apps/explorer/src/design/__tests__/contrast.test.ts b/apps/explorer/src/design/__tests__/contrast.test.ts index 9a6c1a2ef..7ca75c996 100644 --- a/apps/explorer/src/design/__tests__/contrast.test.ts +++ b/apps/explorer/src/design/__tests__/contrast.test.ts @@ -104,6 +104,9 @@ const PAIRS: readonly Pair[] = [ ...['fg', 'fg-2', 'fg-3', 'link', ...STATUS].map((fg) => text(fg, GROUNDS)), text('on-primary', ['primary']), text('on-danger', ['danger']), + // A tag card's Done is filled with its kind colour: the event part's dark + // value in the dark theme, its light value in the light theme. + ...THEMED_EVENTS.map((ground) => text('on-event', [ground])), ...STATUS.map( (status): Pair => ({ ...text('fg', [`${status}-bg`]), over: GROUNDS }), ), diff --git a/apps/explorer/src/design/components/atoms/CodeTokens.tsx b/apps/explorer/src/design/components/atoms/CodeTokens.tsx new file mode 100644 index 000000000..6d5c957d3 --- /dev/null +++ b/apps/explorer/src/design/components/atoms/CodeTokens.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import type { CodeToken } from '../codeTokens'; +import { cx } from '../cx'; + +/** + * Code runs in their colours, for a dark ground: the demos and the code panes + * of product UI. A marked run tints only while `active`. + */ +export function CodeTokens({ + tokens, + active = false, +}: { + tokens: readonly CodeToken[]; + active?: boolean; +}) { + return ( + <> + {tokens.map((token, index) => ( + + {token.t} + + ))} + + ); +} diff --git a/apps/explorer/src/design/components/atoms/InstallCommand.tsx b/apps/explorer/src/design/components/atoms/InstallCommand.tsx index c8c68186b..ada7093ad 100644 --- a/apps/explorer/src/design/components/atoms/InstallCommand.tsx +++ b/apps/explorer/src/design/components/atoms/InstallCommand.tsx @@ -1,12 +1,7 @@ -import React, { - useEffect, - useId, - useRef, - useState, - type ButtonHTMLAttributes, -} from 'react'; +import React, { useId, type ButtonHTMLAttributes } from 'react'; import { cx } from '../cx'; import { Icon } from './Icon'; +import { useCopy } from './useCopy'; export interface InstallCommandProps extends Omit< ButtonHTMLAttributes, @@ -16,24 +11,6 @@ export interface InstallCommandProps extends Omit< command?: string; } -type CopyState = 'idle' | 'done' | 'failed'; - -/** How long the check stays after a successful copy. */ -const CONFIRM_MS = 1400; - -/** - * True only when the clipboard took the text. A missing clipboard API (http, - * older browsers) throws on access, which counts as a failure like a rejection. - */ -async function copyText(text: string): Promise { - try { - await navigator.clipboard.writeText(text); - return true; - } catch { - return false; - } -} - /** * A copy-to-clipboard chip for a CLI command. It confirms with a check only when * the copy succeeded, and says "Copy failed" otherwise. The button's accessible @@ -48,25 +25,7 @@ export function InstallCommand({ ...rest }: InstallCommandProps) { const hintId = useId(); - const [state, setState] = useState('idle'); - const mounted = useRef(true); - const timer = useRef | undefined>(undefined); - - useEffect(() => { - mounted.current = true; - return () => { - mounted.current = false; - clearTimeout(timer.current); - }; - }, []); - - const copy = async (): Promise => { - const copied = await copyText(command); - if (!mounted.current) return; - clearTimeout(timer.current); - setState(copied ? 'done' : 'failed'); - if (copied) timer.current = setTimeout(() => setState('idle'), CONFIRM_MS); - }; + const [state, copy] = useCopy(); return ( <> @@ -75,9 +34,7 @@ export function InstallCommand({ aria-describedby={describedBy ? `${hintId} ${describedBy}` : hintId} type="button" className={cx('elb-cmd', className)} - onClick={() => { - void copy(); - }} + onClick={() => copy(command)} >