altitude-design 0.5.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 (30) hide show
  1. package/dist/altitude.css +9 -2
  2. package/dist/altitude.iife.js +15 -15
  3. package/dist/altitude.no-fonts.css +9 -2
  4. package/dist/guidance/components/AgentFleet/README.md +1 -1
  5. package/dist/guidance/components/Board/README.md +35 -11
  6. package/dist/guidance/components/CommentThread/README.md +3 -1
  7. package/dist/guidance/components/InlineEdit/README.md +1 -1
  8. package/dist/guidance/components/SaveBar/README.md +3 -1
  9. package/dist/guidance/index.json +14 -14
  10. package/dist/guidance/llms.txt +1 -1
  11. package/dist/guidance/manifest.json +20 -20
  12. package/dist/guidance/references/build/checks/agent-action-pattern.mjs +2 -0
  13. package/dist/guidance/references/build/checks/assistant-conversation-pattern.mjs +2 -0
  14. package/dist/guidance/references/build/checks/members-pattern.mjs +2 -0
  15. package/dist/guidance/references/build/checks/settings-pattern.mjs +2 -0
  16. package/dist/guidance/references/build/fixtures/patterns/agent-action.jsx +34 -30
  17. package/dist/guidance/references/build/fixtures/patterns/assistant-conversation.jsx +8 -6
  18. package/dist/guidance/references/build/fixtures/patterns/members.jsx +28 -24
  19. package/dist/guidance/references/build/fixtures/patterns/settings.jsx +34 -30
  20. package/dist/guidance/visual/ActionsInPlace.md +8 -11
  21. package/dist/guidance/visual/Distinctiveness.md +1 -1
  22. package/dist/guidance/visual/Research.md +1 -1
  23. package/dist/guidance/visual/Theming.md +1 -1
  24. package/dist/index.cjs +2 -1
  25. package/dist/index.cjs.map +2 -2
  26. package/dist/index.js +2 -1
  27. package/dist/index.js.map +2 -2
  28. package/dist/types/collections.d.ts +7 -2
  29. package/dist/types-cjs/collections.d.cts +7 -2
  30. package/package.json +1 -1
@@ -32,12 +32,6 @@ function Thread({ restored }) {
32
32
  React.useEffect(() => { if (turn.state === 'complete') setUsed((u) => Math.min(99, u + 9)); }, [turn.state]);
33
33
  return (
34
34
  <div className="col ac">
35
- <A.ToggleGroup type="single" size="sm" aria-label="Make the next send fail" value={failure} onValueChange={(v) => setFailure(/** @type {any} */ (v) || 'none')}>
36
- <A.ToggleGroupItem value="none">Works</A.ToggleGroupItem>
37
- <A.ToggleGroupItem value="offline">Offline</A.ToggleGroupItem>
38
- <A.ToggleGroupItem value="rate-limited">Rate limited</A.ToggleGroupItem>
39
- <A.ToggleGroupItem value="context-full">Context full</A.ToggleGroupItem>
40
- </A.ToggleGroup>
41
35
  <A.Conversation aria-label="Assistant">
42
36
  <A.ConversationContent>
43
37
  {turn.input !== undefined && <A.Message from="user"><A.MessageContent>{turn.input}</A.MessageContent></A.Message>}
@@ -61,6 +55,14 @@ function Thread({ restored }) {
61
55
  <A.PromptInputSubmit />
62
56
  </A.PromptInputToolbar>
63
57
  </A.PromptInput>
58
+ <details className="preview-fixtures"><summary>Example fixtures</summary>
59
+ <A.ToggleGroup type="single" size="sm" aria-label="Make the next send fail" value={failure} onValueChange={(v) => setFailure(/** @type {any} */ (v) || 'none')}>
60
+ <A.ToggleGroupItem value="none">Works</A.ToggleGroupItem>
61
+ <A.ToggleGroupItem value="offline">Offline</A.ToggleGroupItem>
62
+ <A.ToggleGroupItem value="rate-limited">Rate limited</A.ToggleGroupItem>
63
+ <A.ToggleGroupItem value="context-full">Context full</A.ToggleGroupItem>
64
+ </A.ToggleGroup>
65
+ </details>
64
66
  </div>
65
67
  );
66
68
  }
@@ -76,30 +76,34 @@ function App() {
76
76
  setStatus([fresh.length && `${fresh.length} ${fresh.length === 1 ? 'invite' : 'invites'} sent`, already && `${already} ${already === 1 ? 'address is' : 'addresses are'} already in the workspace`].filter(Boolean).join(', ') + '.');
77
77
  };
78
78
  return (
79
- <main className="mb-page">
80
- <h2 id="mb-heading" tabIndex={-1} className="mb-h">Members</h2>
81
- <A.Card className="mb-invite" aria-labelledby="mb-invite-h">
82
- <h3 id="mb-invite-h" className="mb-sub">Invite people</h3>
83
- <A.Field label="Email addresses" hint="Separate addresses with commas, or paste a list.">
84
- <A.TagInput value={emails} onValueChange={setEmails} noun={['address', 'addresses']} placeholder="name@acme.dev" disabled={sending}
85
- normalize={(t) => t.trim().toLowerCase()} validate={(t) => (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t) ? undefined : `${t} isn’t an email address.`)} />
86
- </A.Field>
87
- <div className="mb-roles"><span id="mb-role-label" className="mb-legend">Role</span>
88
- <A.RadioGroup value={role} onValueChange={setRole} aria-labelledby="mb-role-label">{ROLES.map((r) => <A.Radio key={r.value} value={r.value} label={r.label} description={r.description} />)}</A.RadioGroup>
89
- </div>
90
- <div><A.Button variant="primary" loading={sending} disabled={!emails.length} onClick={send}>{sending ? 'Sending invites' : emails.length > 1 ? `Send ${emails.length} invites` : 'Send invite'}</A.Button></div>
91
- </A.Card>
92
- <A.Switch label="Make the next role change fail" checked={failNext} onCheckedChange={setFailNext} />
93
- <A.Table aria-label="Members and invitations">
94
- <A.TableHeader><A.TableRow><A.TableHead>Person</A.TableHead><A.TableHead>Role</A.TableHead><A.TableHead align="end"><span className="mb-vh">Actions</span></A.TableHead></A.TableRow></A.TableHeader>
95
- <A.TableBody>{people.map((p) => <MemberRow key={p.id} person={p} me={me} owners={owners} fail={fail}
96
- onRole={(id, r) => setPeople((l) => l.map((x) => (x.id === id ? { ...x, role: r } : x)))}
97
- onRemove={(x) => { setPeople((l) => l.filter((y) => y.id !== x.id)); setStatus(`${x.name} was removed.`); }}
98
- onResend={(x) => setStatus(`Invite resent to ${x.name}.`)}
99
- onRevoke={(x) => { setPeople((l) => l.filter((y) => y.id !== x.id)); setStatus(`Invitation to ${x.name} revoked.`); document.getElementById('mb-heading')?.focus(); }} />)}</A.TableBody>
100
- </A.Table>
101
- <A.WorkflowStatus>{status}</A.WorkflowStatus>
102
- </main>
79
+ <>
80
+ <main className="mb-page">
81
+ <h2 id="mb-heading" tabIndex={-1} className="mb-h">Members</h2>
82
+ <A.Card className="mb-invite" aria-labelledby="mb-invite-h">
83
+ <h3 id="mb-invite-h" className="mb-sub">Invite people</h3>
84
+ <A.Field label="Email addresses" hint="Separate addresses with commas, or paste a list.">
85
+ <A.TagInput value={emails} onValueChange={setEmails} noun={['address', 'addresses']} placeholder="name@acme.dev" disabled={sending}
86
+ normalize={(t) => t.trim().toLowerCase()} validate={(t) => (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t) ? undefined : `${t} isn’t an email address.`)} />
87
+ </A.Field>
88
+ <div className="mb-roles"><span id="mb-role-label" className="mb-legend">Role</span>
89
+ <A.RadioGroup value={role} onValueChange={setRole} aria-labelledby="mb-role-label">{ROLES.map((r) => <A.Radio key={r.value} value={r.value} label={r.label} description={r.description} />)}</A.RadioGroup>
90
+ </div>
91
+ <div><A.Button variant="primary" loading={sending} disabled={!emails.length} onClick={send}>{sending ? 'Sending invites' : emails.length > 1 ? `Send ${emails.length} invites` : 'Send invite'}</A.Button></div>
92
+ </A.Card>
93
+ <A.Table aria-label="Members and invitations">
94
+ <A.TableHeader><A.TableRow><A.TableHead>Person</A.TableHead><A.TableHead>Role</A.TableHead><A.TableHead align="end"><span className="mb-vh">Actions</span></A.TableHead></A.TableRow></A.TableHeader>
95
+ <A.TableBody>{people.map((p) => <MemberRow key={p.id} person={p} me={me} owners={owners} fail={fail}
96
+ onRole={(id, r) => setPeople((l) => l.map((x) => (x.id === id ? { ...x, role: r } : x)))}
97
+ onRemove={(x) => { setPeople((l) => l.filter((y) => y.id !== x.id)); setStatus(`${x.name} was removed.`); }}
98
+ onResend={(x) => setStatus(`Invite resent to ${x.name}.`)}
99
+ onRevoke={(x) => { setPeople((l) => l.filter((y) => y.id !== x.id)); setStatus(`Invitation to ${x.name} revoked.`); document.getElementById('mb-heading')?.focus(); }} />)}</A.TableBody>
100
+ </A.Table>
101
+ <A.WorkflowStatus>{status}</A.WorkflowStatus>
102
+ </main>
103
+ <details className="preview-fixtures"><summary>Example fixtures</summary>
104
+ <A.Switch label="Make the next role change fail" checked={failNext} onCheckedChange={setFailNext} />
105
+ </details>
106
+ </>
103
107
  );
104
108
  }
105
109
  // preview:end
@@ -37,36 +37,40 @@ function App() {
37
37
  const field = (key, label, hint) => <A.Field label={label} hint={hint} error={general.errors[key]}><A.Input id={'ws-' + key} value={general.draft[key]} onChange={(e) => general.setField(key, e.target.value)} /></A.Field>;
38
38
  if (deleted) return <main className="st-page"><h2 ref={heading} tabIndex={-1} className="st-h">Workspace deleted</h2><p>Acme and everything in it were deleted.</p><A.WorkflowStatus>{remove.status}</A.WorkflowStatus></main>;
39
39
  return (
40
- <main className="st-page">
41
- <h2 ref={heading} tabIndex={-1} className="st-h">Workspace settings</h2>
42
- <nav aria-label="Settings sections" className="st-nav"><a href="#st-general">General</a><a href="#st-notify">Notifications</a><a href="#st-billing">Billing</a><a href="#st-danger">Danger zone</a></nav>
43
- <A.Switch label="Make the next change fail" checked={failNext} onCheckedChange={setFailNext} />
44
- <A.SettingsSection id="st-general" title="General" description="Saved together: change what you need, then save once.">
45
- <form className="st-form" noValidate onSubmit={(e) => { e.preventDefault(); void general.submit(); }}>
46
- <A.ErrorSummary errors={Object.entries(general.errors).map(([k, message]) => ({ id: 'ws-' + k, label: k === 'name' ? 'Workspace name' : 'Workspace URL', message }))} focusKey={general.submitCount} />
47
- {field('name', 'Workspace name', 'Shown to everyone in the workspace.')}
48
- {field('url', 'Workspace URL', 'altitude.app/' + general.draft.url)}
49
- </form>
50
- </A.SettingsSection>
51
- <A.SettingsSection id="st-notify" title="Notifications" description="Each change applies as soon as you make it.">
52
- <A.SettingsRow label="Weekly digest" description="A summary of activity every Monday." status={writes.digest}><A.Switch checked={prefs.digest} onCheckedChange={(v) => apply('digest', v)} /></A.SettingsRow>
53
- <A.SettingsRow label="Mentions" description="Email me when someone mentions me." status={writes.mentions}><A.Switch checked={prefs.mentions} onCheckedChange={(v) => apply('mentions', v)} /></A.SettingsRow>
54
- </A.SettingsSection>
55
- <A.SettingsSection id="st-billing" title="Billing" readOnlyReason="Only workspace owners can change billing details. Ask Maya Chen.">
56
- <A.SettingsRow label="Billing email" description="Invoices and receipts go here."><A.Input defaultValue="finance@acme.dev" /></A.SettingsRow>
57
- </A.SettingsSection>
58
- <A.SettingsSection id="st-danger" title="Danger zone" tone="critical">
59
- <A.SettingsRow label="Delete workspace" description={remove.consequence}><A.Button variant="danger" onClick={remove.request}>Delete workspace</A.Button></A.SettingsRow>
60
- </A.SettingsSection>
61
- <A.WorkflowStatus>{general.status || remove.status}</A.WorkflowStatus>
62
- <A.SaveBar record={general} />
63
- <A.Dialog open={confirming} background="stationary" dismissible={remove.phase !== 'pending'} onOpenChange={(o) => { if (!o) { remove.cancel(); setTyped(''); } }} returnFocus={() => heading.current}
64
- title="Delete Acme workspace?" description={remove.consequence}
65
- footer={<><A.Button disabled={remove.phase === 'pending'} onClick={() => { remove.cancel(); setTyped(''); }}>Keep workspace</A.Button><A.Button variant="danger" disabled={typed !== 'Acme' || remove.phase === 'pending'} onClick={remove.phase === 'failed' ? remove.retry : remove.confirm}>{remove.phase === 'pending' ? 'Deleting workspace' : remove.phase === 'failed' ? 'Try deleting again' : 'Delete workspace'}</A.Button></>}>
66
- <A.Field label="Type Acme to confirm"><A.Input value={typed} onChange={(e) => setTyped(e.target.value)} autoComplete="off" /></A.Field>
67
- {remove.error && <p role="alert">{remove.error}</p>}
68
- </A.Dialog>
69
- </main>
40
+ <>
41
+ <main className="st-page">
42
+ <h2 ref={heading} tabIndex={-1} className="st-h">Workspace settings</h2>
43
+ <nav aria-label="Settings sections" className="st-nav"><a href="#st-general">General</a><a href="#st-notify">Notifications</a><a href="#st-billing">Billing</a><a href="#st-danger">Danger zone</a></nav>
44
+ <A.SettingsSection id="st-general" title="General" description="Saved together: change what you need, then save once.">
45
+ <form className="st-form" noValidate onSubmit={(e) => { e.preventDefault(); void general.submit(); }}>
46
+ <A.ErrorSummary errors={Object.entries(general.errors).map(([k, message]) => ({ id: 'ws-' + k, label: k === 'name' ? 'Workspace name' : 'Workspace URL', message }))} focusKey={general.submitCount} />
47
+ {field('name', 'Workspace name', 'Shown to everyone in the workspace.')}
48
+ {field('url', 'Workspace URL', 'altitude.app/' + general.draft.url)}
49
+ </form>
50
+ </A.SettingsSection>
51
+ <A.SettingsSection id="st-notify" title="Notifications" description="Each change applies as soon as you make it.">
52
+ <A.SettingsRow label="Weekly digest" description="A summary of activity every Monday." status={writes.digest}><A.Switch checked={prefs.digest} onCheckedChange={(v) => apply('digest', v)} /></A.SettingsRow>
53
+ <A.SettingsRow label="Mentions" description="Email me when someone mentions me." status={writes.mentions}><A.Switch checked={prefs.mentions} onCheckedChange={(v) => apply('mentions', v)} /></A.SettingsRow>
54
+ </A.SettingsSection>
55
+ <A.SettingsSection id="st-billing" title="Billing" readOnlyReason="Only workspace owners can change billing details. Ask Maya Chen.">
56
+ <A.SettingsRow label="Billing email" description="Invoices and receipts go here."><A.Input defaultValue="finance@acme.dev" /></A.SettingsRow>
57
+ </A.SettingsSection>
58
+ <A.SettingsSection id="st-danger" title="Danger zone" tone="critical">
59
+ <A.SettingsRow label="Delete workspace" description={remove.consequence}><A.Button variant="danger" onClick={remove.request}>Delete workspace</A.Button></A.SettingsRow>
60
+ </A.SettingsSection>
61
+ <A.WorkflowStatus>{general.status || remove.status}</A.WorkflowStatus>
62
+ <A.SaveBar record={general} />
63
+ <A.Dialog open={confirming} background="stationary" dismissible={remove.phase !== 'pending'} onOpenChange={(o) => { if (!o) { remove.cancel(); setTyped(''); } }} returnFocus={() => heading.current}
64
+ title="Delete Acme workspace?" description={remove.consequence}
65
+ footer={<><A.Button disabled={remove.phase === 'pending'} onClick={() => { remove.cancel(); setTyped(''); }}>Keep workspace</A.Button><A.Button variant="danger" disabled={typed !== 'Acme' || remove.phase === 'pending'} onClick={remove.phase === 'failed' ? remove.retry : remove.confirm}>{remove.phase === 'pending' ? 'Deleting workspace' : remove.phase === 'failed' ? 'Try deleting again' : 'Delete workspace'}</A.Button></>}>
66
+ <A.Field label="Type Acme to confirm"><A.Input value={typed} onChange={(e) => setTyped(e.target.value)} autoComplete="off" /></A.Field>
67
+ {remove.error && <p role="alert">{remove.error}</p>}
68
+ </A.Dialog>
69
+ </main>
70
+ <details className="preview-fixtures"><summary>Example fixtures</summary>
71
+ <A.Switch label="Make the next change fail" checked={failNext} onCheckedChange={setFailNext} />
72
+ </details>
73
+ </>
70
74
  );
71
75
  }
72
76
  // preview:end
@@ -6,12 +6,12 @@ The application chooses that policy from its domain. A danger button does not im
6
6
 
7
7
  ## Try the same example in two views
8
8
 
9
- - [Focused recovery preview](https://github.com/richlaconte/altitude/blob/v0.5.0/project/components/ActionsInPlace/preview.html) — archive, Undo, and restore later from Archived.
10
- - [Complete Files example](https://github.com/richlaconte/altitude/blob/v0.5.0/project/components/ActionsInPlace/preview.html#files) — rename with the exported InlineEdit, archive, recover, and permanently delete from Archived.
11
- - [InlineEdit comparison](https://github.com/richlaconte/altitude/blob/v0.5.0/project/components/InlineEdit/preview.html) and [Dialog presentation comparison](https://github.com/richlaconte/altitude/blob/v0.5.0/project/components/Dialog/preview.html).
9
+ - [Focused recovery preview](https://github.com/richlaconte/altitude/blob/v0.6.0/project/components/ActionsInPlace/preview.html) — archive, Undo, and restore later from Archived.
10
+ - [Complete Files example](https://github.com/richlaconte/altitude/blob/v0.6.0/project/components/ActionsInPlace/preview.html#files) — rename with the exported InlineEdit, archive, recover, and permanently delete from Archived.
11
+ - [InlineEdit comparison](https://github.com/richlaconte/altitude/blob/v0.6.0/project/components/InlineEdit/preview.html) and [Dialog presentation comparison](https://github.com/richlaconte/altitude/blob/v0.6.0/project/components/Dialog/preview.html).
12
12
  - Generated Storybook stories: `Patterns/ActionsInPlace`, `Inputs/InlineEdit`, and `Overlays/Dialog`.
13
13
 
14
- Both Files views use the same source and service in [`build/previews.txt`](https://github.com/richlaconte/altitude/blob/v0.5.0/build/previews.txt). The default has no fixture toolbar in the product. Expand “Example fixtures and source” below the scene to select the deterministic service behavior, reset records/counters, simulate an external change, or inspect accepted requests. “Connection interrupted after save” commits at 100 ms and loses its response at 1,200 ms; the UI must reconcile that result before offering recovery or retry.
14
+ Both Files views use the same source and service in [`build/previews.txt`](https://github.com/richlaconte/altitude/blob/v0.6.0/build/previews.txt). The default has no fixture toolbar in the product. Expand “Example fixtures and source” below the scene to select the deterministic service behavior, reset records/counters, simulate an external change, or inspect accepted requests. “Connection interrupted after save” commits at 100 ms and loses its response at 1,200 ms; the UI must reconcile that result before offering recovery or retry.
15
15
 
16
16
  ## Act and recover
17
17
 
@@ -438,12 +438,9 @@ export default function FilesApp() {
438
438
  .aip-target span { color: var(--ink-muted); font: 400 12.5px/17px var(--font-sans); }
439
439
  .aip-documentation { margin-top: var(--space-6); border-top: 1px solid var(--line); padding-top: var(--space-4); font: 400 12.5px/17px var(--font-sans); }
440
440
  .aip a { color: var(--accent-ink); }
441
- .aip-harness { margin-top: var(--space-4); color: var(--ink-muted); }
442
- .aip-harness summary { cursor: pointer; }
443
- .aip-harness label { display: grid; gap: var(--space-2); }
444
- .aip-harness select { max-width: 100%; font: inherit; padding: var(--space-2); background: var(--surface-well); color: var(--ink); border: 1px solid var(--line-control); border-radius: var(--radius-sm); }
445
- .aip-harness-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
446
- .aip-harness pre { overflow: auto; max-height: 220px; font: 12px/1.5 var(--font-mono); }
441
+ .preview-fixtures label { display: grid; gap: var(--space-2); }
442
+ .preview-fixtures select { max-width: 100%; font: inherit; padding: var(--space-2); background: var(--surface-well); color: var(--ink); border: 1px solid var(--line-control); border-radius: var(--radius-sm); }
443
+ .preview-fixtures pre { overflow: auto; max-height: 220px; font: 12px/1.5 var(--font-mono); }
447
444
  @media (max-width: 600px) { #root { padding: 16px; } .aip-list > li { align-items: stretch; flex-direction: column; gap: var(--space-2); } .aip-row-actions { align-self: flex-start; } .aip-toolbar > .alt-input { max-width: none; } .aip-header > .aip-meta { display: none; } .aip-recovery .alt-alert-inner, .aip-file .alt-alert-inner { flex-wrap: wrap; } .aip-recovery .alt-alert-body, .aip-file .alt-alert-body { flex-basis: calc(100% - 60px); } .aip-recovery .alt-alert-action, .aip-file .alt-alert-action { width: 100%; } }
448
445
  ```
449
446
  <!-- recipe-css:end -->
@@ -458,6 +455,6 @@ Every state has text or an ARIA equivalent when motion and shadows are absent. N
458
455
 
459
456
  ## Verification and scope
460
457
 
461
- [`build/checks/actions-in-place.mjs`](https://github.com/richlaconte/altitude/blob/v0.5.0/build/checks/actions-in-place.mjs) exercises the canonical fixture model and browser interactions under React 18 and 19: duplicate requests, revision conflicts, uncertain outcomes, ordered feedback, dismissed/replaced recovery, targeted deletion, pending Close, old/new dialog sessions, focus recovery, reduced motion, increased contrast, forced colors, and narrow layouts. [The validation record](https://github.com/richlaconte/altitude/blob/v0.5.0/project/ActionsInPlaceValidation.md) distinguishes engineering evidence from remaining assistive-technology and participant gates.
458
+ [`build/checks/actions-in-place.mjs`](https://github.com/richlaconte/altitude/blob/v0.6.0/build/checks/actions-in-place.mjs) exercises the canonical fixture model and browser interactions under React 18 and 19: duplicate requests, revision conflicts, uncertain outcomes, ordered feedback, dismissed/replaced recovery, targeted deletion, pending Close, old/new dialog sessions, focus recovery, reduced motion, increased contrast, forced colors, and narrow layouts. [The validation record](https://github.com/richlaconte/altitude/blob/v0.6.0/project/ActionsInPlaceValidation.md) distinguishes engineering evidence from remaining assistive-technology and participant gates.
462
459
 
463
460
  [EditRegion](../components/EditRegion/README.md) now edits several fields of one record in place with this guide's session rules (issue #12 proposed ItemGroup, PropertyList, DataTable row expansion and Board card editing as its multi-field consumers). ConfirmAction and a shared action hook remain deferred. These demos do not establish two independent real application needs for those abstractions, and they do not establish a measured usability benefit. Structural activation counts and proposed design intent are not participant results.
@@ -2,7 +2,7 @@
2
2
 
3
3
  Interfaces built by agents now have a look people recognise and distrust: purple gradients, Inter, a centred hero, three icon cards, a sidebar and a grid of equal cards on every screen. Altitude is made for apps that agents write, so it can fall into that look twice: through its own defaults, and through what agents compose from them. This chapter says how it stays out, and how we keep checking, because the look keeps moving.
4
4
 
5
- The evidence is in **Research** ("Generated interfaces converge"). Every tell, with its sources and where Altitude stands, is in [`tells.json`](https://github.com/richlaconte/altitude/blob/v0.5.0/project/tells.json). Looking generated is half the problem; the other half is breaking established rules (hierarchy, labelling, recovery, honesty), which [Fundamentals](Fundamentals.md) covers.
5
+ The evidence is in **Research** ("Generated interfaces converge"). Every tell, with its sources and where Altitude stands, is in [`tells.json`](https://github.com/richlaconte/altitude/blob/v0.6.0/project/tells.json). Looking generated is half the problem; the other half is breaking established rules (hierarchy, labelling, recovery, honesty), which [Fundamentals](Fundamentals.md) covers.
6
6
 
7
7
  ## Why it happens
8
8
 
@@ -622,7 +622,7 @@ A measurement, then a design review ([#97](https://github.com/richlaconte/altitu
622
622
  - **A pointer over the list holds the close.** A delayed close could move the next row under a pointer reaching for it. The close waits while a mouse is over the list, capped at `dur-settle`; touch has no hover, so it doesn't wait.
623
623
  - **`dur-linger` is 2000ms by judgement.** About four words read in roughly a second (Nielsen's 1s flow limit) plus a second to act; the top of the "second or two" asked for.
624
624
 
625
- Not yet tested with people: whether the close is noticeable enough at `dur-travel-3`, and whether waiting for the pointer to leave the list reads as lag. Both are open in [the validation record](https://github.com/richlaconte/altitude/blob/v0.5.0/project/ActionsInPlaceValidation.md).
625
+ Not yet tested with people: whether the close is noticeable enough at `dur-travel-3`, and whether waiting for the pointer to leave the list reads as lag. Both are open in [the validation record](https://github.com/richlaconte/altitude/blob/v0.6.0/project/ActionsInPlaceValidation.md).
626
626
 
627
627
  ### E47 · Can a chart Altitude doesn't draw keep its rules?
628
628
 
@@ -165,7 +165,7 @@ That gets shadcn's components onto Altitude's colors and shadows. The elevation
165
165
 
166
166
  ### Charts
167
167
 
168
- shadcn/ui's `Chart` is Recharts underneath (Tremor wraps Recharts too), so charts already built with it can take Altitude's colours and most of its chart rules without being rewritten. What they can't take is listed at the end. [`examples/shadcn-chart`](https://github.com/richlaconte/altitude/blob/v0.5.0/examples/shadcn-chart) is this setup, checked in both schemes by `build/checks/shadcn-chart.mjs`.
168
+ shadcn/ui's `Chart` is Recharts underneath (Tremor wraps Recharts too), so charts already built with it can take Altitude's colours and most of its chart rules without being rewritten. What they can't take is listed at the end. [`examples/shadcn-chart`](https://github.com/richlaconte/altitude/blob/v0.6.0/examples/shadcn-chart) is this setup, checked in both schemes by `build/checks/shadcn-chart.mjs`.
169
169
 
170
170
  **Map the palette.** Add the chart lines to the `@theme inline` block above, so Tailwind's `fill-chart-1` and the like are Altitude's slots:
171
171
 
package/dist/index.cjs CHANGED
@@ -12249,6 +12249,7 @@ var BoardCard = React65.forwardRef(function BoardCard2({
12249
12249
  label,
12250
12250
  heldBy,
12251
12251
  settle,
12252
+ status,
12252
12253
  onRename,
12253
12254
  value,
12254
12255
  renderEditor,
@@ -12265,7 +12266,7 @@ var BoardCard = React65.forwardRef(function BoardCard2({
12265
12266
  const [mode, setMode] = React65.useState(null);
12266
12267
  const self = React65.useRef(null);
12267
12268
  const name = label ?? id;
12268
- const depth = useDepth({ heldBy, settle });
12269
+ const depth = useDepth({ heldBy, settle, status });
12269
12270
  const refocus = React65.useRef(false);
12270
12271
  const done = () => {
12271
12272
  refocus.current = true;