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.
- package/dist/altitude.css +9 -2
- package/dist/altitude.iife.js +15 -15
- package/dist/altitude.no-fonts.css +9 -2
- package/dist/guidance/components/AgentFleet/README.md +1 -1
- package/dist/guidance/components/Board/README.md +35 -11
- package/dist/guidance/components/CommentThread/README.md +3 -1
- package/dist/guidance/components/InlineEdit/README.md +1 -1
- package/dist/guidance/components/SaveBar/README.md +3 -1
- package/dist/guidance/index.json +14 -14
- package/dist/guidance/llms.txt +1 -1
- package/dist/guidance/manifest.json +20 -20
- package/dist/guidance/references/build/checks/agent-action-pattern.mjs +2 -0
- package/dist/guidance/references/build/checks/assistant-conversation-pattern.mjs +2 -0
- package/dist/guidance/references/build/checks/members-pattern.mjs +2 -0
- package/dist/guidance/references/build/checks/settings-pattern.mjs +2 -0
- package/dist/guidance/references/build/fixtures/patterns/agent-action.jsx +34 -30
- package/dist/guidance/references/build/fixtures/patterns/assistant-conversation.jsx +8 -6
- package/dist/guidance/references/build/fixtures/patterns/members.jsx +28 -24
- package/dist/guidance/references/build/fixtures/patterns/settings.jsx +34 -30
- package/dist/guidance/visual/ActionsInPlace.md +8 -11
- package/dist/guidance/visual/Distinctiveness.md +1 -1
- package/dist/guidance/visual/Research.md +1 -1
- package/dist/guidance/visual/Theming.md +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +2 -2
- package/dist/index.js +2 -1
- package/dist/index.js.map +2 -2
- package/dist/types/collections.d.ts +7 -2
- package/dist/types-cjs/collections.d.cts +7 -2
- 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
|
-
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
<A.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
|
10
|
-
- [Complete Files example](https://github.com/richlaconte/altitude/blob/v0.
|
|
11
|
-
- [InlineEdit comparison](https://github.com/richlaconte/altitude/blob/v0.
|
|
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.
|
|
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
|
-
.
|
|
442
|
-
.
|
|
443
|
-
.
|
|
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.
|
|
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
|
+
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.
|
|
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.
|
|
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;
|