altitude-design 0.3.2 → 0.4.1
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/README.md +2 -0
- package/dist/cli/altitude.mjs +120 -20
- package/dist/guidance/README.md +4 -0
- package/dist/guidance/agent-setup.md +3 -2
- package/dist/guidance/components/AILabel/README.md +60 -0
- package/dist/guidance/components/Accordion/README.md +32 -0
- package/dist/guidance/components/AgentFleet/README.md +81 -0
- package/dist/guidance/components/AgentLanes/README.md +53 -0
- package/dist/guidance/components/AgentRequests/README.md +76 -0
- package/dist/guidance/components/Alert/README.md +71 -0
- package/dist/guidance/components/AltitudeProvider/README.md +111 -0
- package/dist/guidance/components/AppShell/README.md +121 -0
- package/dist/guidance/components/AreaChart/README.md +101 -0
- package/dist/guidance/components/Artifact/README.md +99 -0
- package/dist/guidance/components/Avatar/README.md +75 -0
- package/dist/guidance/components/Await/README.md +128 -0
- package/dist/guidance/components/Badge/README.md +66 -0
- package/dist/guidance/components/BarChart/README.md +109 -0
- package/dist/guidance/components/BarList/README.md +87 -0
- package/dist/guidance/components/Board/README.md +98 -0
- package/dist/guidance/components/Breadcrumb/README.md +66 -0
- package/dist/guidance/components/Button/README.md +58 -0
- package/dist/guidance/components/Card/README.md +52 -0
- package/dist/guidance/components/ChartBrush/README.md +98 -0
- package/dist/guidance/components/ChartContainer/README.md +229 -0
- package/dist/guidance/components/ChartLegend/README.md +78 -0
- package/dist/guidance/components/ChartReference/README.md +115 -0
- package/dist/guidance/components/ChartTooltip/README.md +101 -0
- package/dist/guidance/components/Checkbox/README.md +46 -0
- package/dist/guidance/components/Checklist/README.md +137 -0
- package/dist/guidance/components/CodeBlock/README.md +72 -0
- package/dist/guidance/components/ColorPicker/README.md +69 -0
- package/dist/guidance/components/Combobox/README.md +51 -0
- package/dist/guidance/components/CommandPalette/README.md +204 -0
- package/dist/guidance/components/CommentThread/README.md +81 -0
- package/dist/guidance/components/Confirmation/README.md +94 -0
- package/dist/guidance/components/ContextMenu/README.md +81 -0
- package/dist/guidance/components/Conversation/README.md +169 -0
- package/dist/guidance/components/CopyButton/README.md +54 -0
- package/dist/guidance/components/DataGrid/README.md +157 -0
- package/dist/guidance/components/DatePicker/README.md +116 -0
- package/dist/guidance/components/Dialog/README.md +204 -0
- package/dist/guidance/components/DiffView/README.md +107 -0
- package/dist/guidance/components/DotPlot/README.md +82 -0
- package/dist/guidance/components/Drilldown/README.md +43 -0
- package/dist/guidance/components/DropZone/README.md +71 -0
- package/dist/guidance/components/DropdownMenu/README.md +63 -0
- package/dist/guidance/components/EditRegion/README.md +263 -0
- package/dist/guidance/components/EmptyState/README.md +66 -0
- package/dist/guidance/components/FeatureGate/README.md +67 -0
- package/dist/guidance/components/Field/README.md +46 -0
- package/dist/guidance/components/FilterBar/README.md +132 -0
- package/dist/guidance/components/FormattedNumber/README.md +81 -0
- package/dist/guidance/components/Heatmap/README.md +116 -0
- package/dist/guidance/components/HoldButton/README.md +48 -0
- package/dist/guidance/components/HoverCard/README.md +71 -0
- package/dist/guidance/components/InlineCitation/README.md +56 -0
- package/dist/guidance/components/InlineEdit/README.md +379 -0
- package/dist/guidance/components/Input/README.md +63 -0
- package/dist/guidance/components/InputOTP/README.md +76 -0
- package/dist/guidance/components/ItemGroup/README.md +193 -0
- package/dist/guidance/components/Kbd/README.md +36 -0
- package/dist/guidance/components/LineChart/README.md +109 -0
- package/dist/guidance/components/Link/README.md +69 -0
- package/dist/guidance/components/Markdown/README.md +113 -0
- package/dist/guidance/components/Meter/README.md +81 -0
- package/dist/guidance/components/NotificationCenter/README.md +67 -0
- package/dist/guidance/components/NumberInput/README.md +66 -0
- package/dist/guidance/components/PageHeader/README.md +74 -0
- package/dist/guidance/components/Pagination/README.md +58 -0
- package/dist/guidance/components/PasswordInput/README.md +70 -0
- package/dist/guidance/components/PermissionMatrix/README.md +68 -0
- package/dist/guidance/components/Pile/README.md +54 -0
- package/dist/guidance/components/PlanPicker/README.md +62 -0
- package/dist/guidance/components/Popover/README.md +48 -0
- package/dist/guidance/components/Progress/README.md +41 -0
- package/dist/guidance/components/PromptInput/README.md +138 -0
- package/dist/guidance/components/PropertyList/README.md +137 -0
- package/dist/guidance/components/Question/README.md +81 -0
- package/dist/guidance/components/RadioGroup/README.md +47 -0
- package/dist/guidance/components/RelativeTime/README.md +73 -0
- package/dist/guidance/components/RemovalReceipt/README.md +111 -0
- package/dist/guidance/components/Resizable/README.md +65 -0
- package/dist/guidance/components/SaveBar/README.md +65 -0
- package/dist/guidance/components/SavedViews/README.md +82 -0
- package/dist/guidance/components/ScatterChart/README.md +104 -0
- package/dist/guidance/components/Schedule/README.md +142 -0
- package/dist/guidance/components/ScrollArea/README.md +35 -0
- package/dist/guidance/components/SecretField/README.md +74 -0
- package/dist/guidance/components/Select/README.md +97 -0
- package/dist/guidance/components/Separator/README.md +70 -0
- package/dist/guidance/components/Sheet/README.md +126 -0
- package/dist/guidance/components/Skeleton/README.md +50 -0
- package/dist/guidance/components/Slider/README.md +60 -0
- package/dist/guidance/components/SortableList/README.md +45 -0
- package/dist/guidance/components/Sparkline/README.md +87 -0
- package/dist/guidance/components/SplitButton/README.md +65 -0
- package/dist/guidance/components/Stat/README.md +92 -0
- package/dist/guidance/components/Stepper/README.md +90 -0
- package/dist/guidance/components/Suggestions/README.md +87 -0
- package/dist/guidance/components/Surface/README.md +48 -0
- package/dist/guidance/components/Switch/README.md +38 -0
- package/dist/guidance/components/SyncIndicator/README.md +78 -0
- package/dist/guidance/components/Table/README.md +138 -0
- package/dist/guidance/components/Tabs/README.md +73 -0
- package/dist/guidance/components/TagInput/README.md +116 -0
- package/dist/guidance/components/Terminal/README.md +69 -0
- package/dist/guidance/components/ThreadList/README.md +110 -0
- package/dist/guidance/components/TimeInput/README.md +63 -0
- package/dist/guidance/components/Timeline/README.md +87 -0
- package/dist/guidance/components/Toaster/README.md +54 -0
- package/dist/guidance/components/Toggle/README.md +85 -0
- package/dist/guidance/components/Toolbar/README.md +106 -0
- package/dist/guidance/components/Tooltip/README.md +41 -0
- package/dist/guidance/components/Tour/README.md +73 -0
- package/dist/guidance/components/Trace/README.md +129 -0
- package/dist/guidance/components/Tree/README.md +171 -0
- package/dist/guidance/evidence.json +18 -0
- package/dist/guidance/index.json +811 -7
- package/dist/guidance/llms.txt +153 -2
- package/dist/guidance/manifest.json +133 -12
- package/dist/guidance/patterns/screen-layout.md +1 -1
- package/dist/guidance/references/build/checks/agent-setup.mjs +7 -0
- package/dist/guidance/references/build/checks/guidance.mjs +8 -2
- package/dist/guidance/references/build/checks/source-check.mjs +101 -0
- package/dist/guidance/rules.json +155 -0
- package/dist/guidance/visual/ActionsInPlace.md +463 -0
- package/dist/guidance/visual/Charts.md +119 -0
- package/dist/guidance/visual/Distinctiveness.md +106 -0
- package/dist/guidance/visual/Elevation.md +1 -1
- package/dist/guidance/visual/Mobile.md +64 -0
- package/dist/guidance/visual/Motion.md +225 -0
- package/dist/guidance/visual/README.md +98 -0
- package/dist/guidance/visual/Research.md +1 -1
- package/dist/guidance/visual/Theming.md +349 -0
- package/dist/types/agent-fleet.d.ts +28 -0
- package/dist/types/ai-label.d.ts +7 -0
- package/dist/types/app-shell.d.ts +23 -0
- package/dist/types/area-chart.d.ts +8 -0
- package/dist/types/artifact.d.ts +14 -0
- package/dist/types/bar-chart.d.ts +6 -0
- package/dist/types/bar-list.d.ts +6 -0
- package/dist/types/breadcrumb.d.ts +6 -0
- package/dist/types/chart-brush.d.ts +6 -0
- package/dist/types/chart.d.ts +29 -1
- package/dist/types/checklist.d.ts +6 -0
- package/dist/types/code-block.d.ts +6 -0
- package/dist/types/collections.d.ts +15 -0
- package/dist/types/color-picker.d.ts +6 -0
- package/dist/types/command.d.ts +13 -0
- package/dist/types/comment-thread.d.ts +11 -0
- package/dist/types/confirmation.d.ts +8 -0
- package/dist/types/context-menu.d.ts +11 -0
- package/dist/types/controls.d.ts +174 -10
- package/dist/types/conversation.d.ts +21 -0
- package/dist/types/copy-button.d.ts +6 -0
- package/dist/types/core.d.ts +31 -0
- package/dist/types/data-grid.d.ts +25 -0
- package/dist/types/date-picker.d.ts +6 -0
- package/dist/types/diff-view.d.ts +8 -0
- package/dist/types/dot-plot.d.ts +6 -0
- package/dist/types/edit-region.d.ts +23 -0
- package/dist/types/empty.d.ts +9 -0
- package/dist/types/feature-gate.d.ts +8 -0
- package/dist/types/filter-bar.d.ts +8 -0
- package/dist/types/formatted-number.d.ts +6 -0
- package/dist/types/heatmap.d.ts +6 -0
- package/dist/types/inline-citation.d.ts +6 -0
- package/dist/types/inline-edit.d.ts +7 -0
- package/dist/types/input-otp.d.ts +11 -0
- package/dist/types/item-group.d.ts +29 -0
- package/dist/types/line-chart.d.ts +6 -0
- package/dist/types/link.d.ts +8 -1
- package/dist/types/markdown.d.ts +6 -0
- package/dist/types/meter.d.ts +6 -0
- package/dist/types/nav.d.ts +6 -0
- package/dist/types/notifications.d.ts +6 -0
- package/dist/types/number-input.d.ts +6 -0
- package/dist/types/overlays.d.ts +61 -0
- package/dist/types/page-header.d.ts +14 -0
- package/dist/types/pagination.d.ts +6 -0
- package/dist/types/password-input.d.ts +6 -0
- package/dist/types/permission-matrix.d.ts +7 -0
- package/dist/types/pile.d.ts +6 -0
- package/dist/types/plan-picker.d.ts +6 -0
- package/dist/types/prompt-input.d.ts +11 -0
- package/dist/types/property-list.d.ts +17 -0
- package/dist/types/question.d.ts +11 -0
- package/dist/types/relative-time.d.ts +8 -1
- package/dist/types/removal-receipt.d.ts +18 -1
- package/dist/types/save-bar.d.ts +6 -0
- package/dist/types/saved-views.d.ts +12 -0
- package/dist/types/scatter-chart.d.ts +8 -0
- package/dist/types/schedule.d.ts +21 -0
- package/dist/types/secret-field.d.ts +6 -0
- package/dist/types/select.d.ts +12 -0
- package/dist/types/separator.d.ts +8 -1
- package/dist/types/sheet.d.ts +19 -0
- package/dist/types/sparkline.d.ts +6 -0
- package/dist/types/split-button.d.ts +7 -0
- package/dist/types/stat.d.ts +7 -0
- package/dist/types/stepper.d.ts +12 -0
- package/dist/types/suggestions.d.ts +10 -0
- package/dist/types/sync-indicator.d.ts +8 -0
- package/dist/types/tag-input.d.ts +8 -0
- package/dist/types/terminal.d.ts +6 -0
- package/dist/types/thread-list.d.ts +16 -0
- package/dist/types/time-input.d.ts +6 -0
- package/dist/types/timeline.d.ts +9 -0
- package/dist/types/toggle.d.ts +6 -0
- package/dist/types/toolbar.d.ts +20 -0
- package/dist/types/tour.d.ts +10 -0
- package/dist/types/trace.d.ts +16 -0
- package/dist/types/tree.d.ts +21 -0
- package/dist/types-cjs/agent-fleet.d.cts +28 -0
- package/dist/types-cjs/ai-label.d.cts +7 -0
- package/dist/types-cjs/app-shell.d.cts +23 -0
- package/dist/types-cjs/area-chart.d.cts +8 -0
- package/dist/types-cjs/artifact.d.cts +14 -0
- package/dist/types-cjs/bar-chart.d.cts +6 -0
- package/dist/types-cjs/bar-list.d.cts +6 -0
- package/dist/types-cjs/breadcrumb.d.cts +6 -0
- package/dist/types-cjs/chart-brush.d.cts +6 -0
- package/dist/types-cjs/chart.d.cts +29 -1
- package/dist/types-cjs/checklist.d.cts +6 -0
- package/dist/types-cjs/code-block.d.cts +6 -0
- package/dist/types-cjs/collections.d.cts +15 -0
- package/dist/types-cjs/color-picker.d.cts +6 -0
- package/dist/types-cjs/command.d.cts +13 -0
- package/dist/types-cjs/comment-thread.d.cts +11 -0
- package/dist/types-cjs/confirmation.d.cts +8 -0
- package/dist/types-cjs/context-menu.d.cts +11 -0
- package/dist/types-cjs/controls.d.cts +174 -10
- package/dist/types-cjs/conversation.d.cts +21 -0
- package/dist/types-cjs/copy-button.d.cts +6 -0
- package/dist/types-cjs/core.d.cts +31 -0
- package/dist/types-cjs/data-grid.d.cts +25 -0
- package/dist/types-cjs/date-picker.d.cts +6 -0
- package/dist/types-cjs/diff-view.d.cts +8 -0
- package/dist/types-cjs/dot-plot.d.cts +6 -0
- package/dist/types-cjs/edit-region.d.cts +23 -0
- package/dist/types-cjs/empty.d.cts +9 -0
- package/dist/types-cjs/feature-gate.d.cts +8 -0
- package/dist/types-cjs/filter-bar.d.cts +8 -0
- package/dist/types-cjs/formatted-number.d.cts +6 -0
- package/dist/types-cjs/heatmap.d.cts +6 -0
- package/dist/types-cjs/inline-citation.d.cts +6 -0
- package/dist/types-cjs/inline-edit.d.cts +7 -0
- package/dist/types-cjs/input-otp.d.cts +11 -0
- package/dist/types-cjs/item-group.d.cts +29 -0
- package/dist/types-cjs/line-chart.d.cts +6 -0
- package/dist/types-cjs/link.d.cts +8 -1
- package/dist/types-cjs/markdown.d.cts +6 -0
- package/dist/types-cjs/meter.d.cts +6 -0
- package/dist/types-cjs/nav.d.cts +6 -0
- package/dist/types-cjs/notifications.d.cts +6 -0
- package/dist/types-cjs/number-input.d.cts +6 -0
- package/dist/types-cjs/overlays.d.cts +61 -0
- package/dist/types-cjs/page-header.d.cts +14 -0
- package/dist/types-cjs/pagination.d.cts +6 -0
- package/dist/types-cjs/password-input.d.cts +6 -0
- package/dist/types-cjs/permission-matrix.d.cts +7 -0
- package/dist/types-cjs/pile.d.cts +6 -0
- package/dist/types-cjs/plan-picker.d.cts +6 -0
- package/dist/types-cjs/prompt-input.d.cts +11 -0
- package/dist/types-cjs/property-list.d.cts +17 -0
- package/dist/types-cjs/question.d.cts +11 -0
- package/dist/types-cjs/relative-time.d.cts +8 -1
- package/dist/types-cjs/removal-receipt.d.cts +18 -1
- package/dist/types-cjs/save-bar.d.cts +6 -0
- package/dist/types-cjs/saved-views.d.cts +12 -0
- package/dist/types-cjs/scatter-chart.d.cts +8 -0
- package/dist/types-cjs/schedule.d.cts +21 -0
- package/dist/types-cjs/secret-field.d.cts +6 -0
- package/dist/types-cjs/select.d.cts +12 -0
- package/dist/types-cjs/separator.d.cts +8 -1
- package/dist/types-cjs/sheet.d.cts +19 -0
- package/dist/types-cjs/sparkline.d.cts +6 -0
- package/dist/types-cjs/split-button.d.cts +7 -0
- package/dist/types-cjs/stat.d.cts +7 -0
- package/dist/types-cjs/stepper.d.cts +12 -0
- package/dist/types-cjs/suggestions.d.cts +10 -0
- package/dist/types-cjs/sync-indicator.d.cts +8 -0
- package/dist/types-cjs/tag-input.d.cts +8 -0
- package/dist/types-cjs/terminal.d.cts +6 -0
- package/dist/types-cjs/thread-list.d.cts +16 -0
- package/dist/types-cjs/time-input.d.cts +6 -0
- package/dist/types-cjs/timeline.d.cts +9 -0
- package/dist/types-cjs/toggle.d.cts +6 -0
- package/dist/types-cjs/toolbar.d.cts +20 -0
- package/dist/types-cjs/tour.d.cts +10 -0
- package/dist/types-cjs/trace.d.cts +16 -0
- package/dist/types-cjs/tree.d.cts +21 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
A design and HCI framework for agent-built React web applications, with a component foundation where **height is the interface**. Its one idea: the ground is the record. Settled content sits on the ground; anything live rises above it; anything waiting to receive sinks below it.
|
|
4
4
|
|
|
5
5
|
- **Site:** https://richlaconte.github.io/altitude/
|
|
6
|
+
- **For agents:** https://richlaconte.github.io/altitude/llms.txt, an index of the guides, task patterns and every component's guide as plain Markdown ([llmstxt.org](https://llmstxt.org)), and `llms-full.txt` beside it with every guide in one file
|
|
6
7
|
- **Storybook:** every component, with its guidelines and a live preview in both schemes: https://richlaconte.github.io/altitude/storybook/
|
|
7
8
|
- **The design guide:** [`project/README.md`](https://github.com/richlaconte/altitude/blob/main/project/README.md), then [Elevation](https://github.com/richlaconte/altitude/blob/main/project/Elevation.md), [Motion](https://github.com/richlaconte/altitude/blob/main/project/Motion.md), [Charts](https://github.com/richlaconte/altitude/blob/main/project/Charts.md), [Theming](https://github.com/richlaconte/altitude/blob/main/project/Theming.md), [Fundamentals](https://github.com/richlaconte/altitude/blob/main/project/Fundamentals.md), [Distinctiveness](https://github.com/richlaconte/altitude/blob/main/project/Distinctiveness.md) and [Research](https://github.com/richlaconte/altitude/blob/main/project/Research.md)
|
|
8
9
|
|
|
@@ -117,6 +118,7 @@ npx altitude init --client codex # preview exact changes
|
|
|
117
118
|
npx altitude init --client codex --apply # apply after reviewing the preview
|
|
118
119
|
# Claude Code: --client claude. Both: --client all.
|
|
119
120
|
npx altitude doctor --json
|
|
121
|
+
npx altitude check # Altitude's rules that are certain from your source
|
|
120
122
|
```
|
|
121
123
|
|
|
122
124
|
Setup preserves existing instructions and adds a small versioned pointer to the installed guidance. It never runs on package installation. Preview removal with `npx altitude init --client codex --remove`, then add `--apply` to remove only its managed content. Edited blocks are preserved and require reconciliation. Codex uses a nonempty root `AGENTS.override.md` when present; otherwise `AGENTS.md`. Claude Code uses `CLAUDE.md`. Restart the client after setup. See [setup details](docs/framework/AGENT-SETUP.md).
|
package/dist/cli/altitude.mjs
CHANGED
|
@@ -1,10 +1,85 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// scripts/altitude-cli.mjs
|
|
4
|
-
import { existsSync, lstatSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
4
|
+
import { existsSync, lstatSync, readFileSync as readFileSync2, writeFileSync, unlinkSync } from "node:fs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { createHash } from "node:crypto";
|
|
7
|
-
import { resolve, dirname, relative, basename } from "node:path";
|
|
7
|
+
import { resolve, dirname, relative as relative2, basename } from "node:path";
|
|
8
|
+
|
|
9
|
+
// scripts/source-check.mjs
|
|
10
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
|
+
import { extname, join, relative } from "node:path";
|
|
12
|
+
var SOURCE = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js", ".mjs", ".css", ".scss", ".html"]);
|
|
13
|
+
var SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", ".next", ".git", "coverage", "storybook-static"]);
|
|
14
|
+
var COLOUR = /#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})\b|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(/;
|
|
15
|
+
var TAILWIND_COLOUR = /(?:^|[\s"'`:])(?:text|bg|border|ring|outline|fill|stroke|from|via|to|divide|placeholder|decoration|caret|accent)-(?:(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|[1-9]00|950)|white|black|\[(?:#|rgb|hsl|oklch))/;
|
|
16
|
+
function files(dir) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
19
|
+
if (SKIP.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
20
|
+
const path = join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) out.push(...files(path));
|
|
22
|
+
else if (SOURCE.has(extname(entry.name)) && !/\.(test|spec|stories)\.[jt]sx?$|\.d\.ts$/.test(entry.name) && statSync(path).size < 1e6) out.push(path);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
var ignored = (lines, i, id) => [lines[i], lines[i - 1] || ""].some((l) => new RegExp(`altitude-ignore:\\s*${id}\\b`).test(l));
|
|
27
|
+
var uncomment = (text) => text.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " ")).replace(/(^|[^:'"`])\/\/.*$/gm, "$1");
|
|
28
|
+
var RULES = {
|
|
29
|
+
"ALT-TOKEN-001": { kind: "deterministic", title: "Colours come from the tokens, not literals" },
|
|
30
|
+
"ALT-SETUP-002": { kind: "deterministic", title: "Altitude's stylesheet is imported" },
|
|
31
|
+
"ALT-SETUP-003": { kind: "deterministic", title: "The app is wrapped in AltitudeProvider" },
|
|
32
|
+
"ALT-CHART-001": { kind: "deterministic", title: "No pie or donut charts" },
|
|
33
|
+
"ALT-COPY-001": { kind: "deterministic", title: "Buttons name their outcome, never OK or Submit" },
|
|
34
|
+
"ALT-PRIMARY-001": { kind: "contextual-review", title: "One primary action per view" },
|
|
35
|
+
"ALT-VISUAL-004": { kind: "contextual-review", title: "Controls keep Altitude's elevation" }
|
|
36
|
+
};
|
|
37
|
+
function scan(root, { tokenNames = /* @__PURE__ */ new Set() } = {}) {
|
|
38
|
+
const findings = [];
|
|
39
|
+
const add = (id, file, line, observed, repair) => findings.push({ ruleId: id, kind: RULES[id].kind, status: RULES[id].kind === "deterministic" ? "fail" : "review", file: file && relative(root, file), line, observed, expected: RULES[id].title, repair, verification: `altitude explain ${id}` });
|
|
40
|
+
const sources = files(root);
|
|
41
|
+
let usesAltitude = false, stylesheet = false, providers = [];
|
|
42
|
+
for (const file of sources) {
|
|
43
|
+
const raw = readFileSync(file, "utf8");
|
|
44
|
+
const text = uncomment(raw);
|
|
45
|
+
const lines = text.split("\n"), rawLines = raw.split("\n");
|
|
46
|
+
const css = /\.(s?css|html)$/.test(file);
|
|
47
|
+
if (/from\s+['"]altitude-design['"]|require\(\s*['"]altitude-design['"]\s*\)/.test(text)) usesAltitude = true;
|
|
48
|
+
if (/['"]altitude-design\/altitude(\.no-fonts)?\.css['"]/.test(text)) stylesheet = true;
|
|
49
|
+
lines.forEach((line, i) => {
|
|
50
|
+
const at = i + 1;
|
|
51
|
+
const skip = (id) => ignored(rawLines, i, id);
|
|
52
|
+
if (COLOUR.test(line) && !skip("ALT-TOKEN-001")) {
|
|
53
|
+
const definesToken = /^\s*--[\w-]+\s*:/.test(line) || [...line.matchAll(/['"]?([a-z][\w-]*)['"]?\s*:\s*['"`]?(?:#|rgb|hsl|oklch)/g)].some((m) => tokenNames.has(m[1]));
|
|
54
|
+
const accent = /\baccent\s*=\s*\{?\s*['"`]#/.test(line);
|
|
55
|
+
const inStyle = css ? [...line.matchAll(/(?:^|[{;"'])\s*([a-z-]+)\s*:\s*([^;{}"']+)/g)].some((m) => !m[1].startsWith("--") && COLOUR.test(m[2])) : /\bstyle=\{\{|(?:color|background|backgroundColor|borderColor|fill|stroke|boxShadow|outlineColor)\s*[:=]/.test(line);
|
|
56
|
+
if (!definesToken && !accent && inStyle) add("ALT-TOKEN-001", file, at, line.trim().slice(0, 160), "Use a token instead: var(--ink-muted), var(--accent), var(--surface-raised) and the rest of tokens.css. A brand colour goes to AltitudeProvider accent, and a full palette to a custom scheme.");
|
|
57
|
+
} else if (!css && TAILWIND_COLOUR.test(line) && /\bclass(Name)?=/.test(line) && !skip("ALT-TOKEN-001")) {
|
|
58
|
+
add("ALT-TOKEN-001", file, at, line.trim().slice(0, 160), 'Use a token instead of a palette class: style={{ color: "var(--ink-muted)" }} or a class whose CSS uses the tokens.');
|
|
59
|
+
}
|
|
60
|
+
if (css) return;
|
|
61
|
+
if (!skip("ALT-CHART-001") && (/import\s*\{[^}]*\b(PieChart|Pie|Doughnut|RadialBarChart)\b[^}]*\}\s*from\s*['"](recharts|react-chartjs-2)['"]/.test(line) || /from\s*['"]@nivo\/pie['"]/.test(line) || /\btype\s*:\s*['"](pie|doughnut)['"]/.test(line)))
|
|
62
|
+
add("ALT-CHART-001", file, at, line.trim().slice(0, 160), 'Show a part of a whole as a stacked BarChart or a segmented Meter: angle and area are read less accurately than length (Charts.md, "Choosing a form").');
|
|
63
|
+
const copy = line.match(/<(?:\w+\.)?Button\b[^>]*>\s*(OK|Ok|Okay|Submit)\s*<\/(?:\w+\.)?Button>/);
|
|
64
|
+
if (copy && !skip("ALT-COPY-001")) add("ALT-COPY-001", file, at, copy[0].slice(0, 160), `Start with a verb and name the outcome: "Save changes", "Send invite", "Create project", not "${copy[1]}".`);
|
|
65
|
+
const lift = line.match(/<(?:\w+\.)?(Input|Textarea|Select|Combobox|Button|Switch|Checkbox)\b[^>]*(boxShadow|box-shadow|\bshadow-(?!none)[\w-]+|\btranslate-y-|\btransform\s*:)/);
|
|
66
|
+
if (lift && !skip("ALT-VISUAL-004")) add("ALT-VISUAL-004", file, at, line.trim().slice(0, 160), `Remove the override: a ${lift[1]}'s height is how it says whether you press it or fill it. Use the component's variant or size props instead.`);
|
|
67
|
+
});
|
|
68
|
+
if (!css) {
|
|
69
|
+
const n = (text.match(/<(?:\w+\.)?AltitudeProvider\b/g) || []).length;
|
|
70
|
+
if (n) providers.push(...Array(n).fill(relative(root, file)));
|
|
71
|
+
const primaries = [...text.matchAll(/<(?:\w+\.)?Button\b[^>]*\bvariant=["']primary["']/g)];
|
|
72
|
+
if (primaries.length > 1 && !ignored(rawLines, text.slice(0, primaries[1].index).split("\n").length - 1, "ALT-PRIMARY-001"))
|
|
73
|
+
add("ALT-PRIMARY-001", file, text.slice(0, primaries[1].index).split("\n").length, `${primaries.length} primary buttons in this file`, "If they can be on screen together, keep one primary (the one action the task leads to) and make the others secondary or ghost.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (usesAltitude && !stylesheet) add("ALT-SETUP-002", null, null, "No file imports altitude-design/altitude.css or altitude-design/altitude.no-fonts.css", "Import one of them once, at the app's entry: import 'altitude-design/altitude.css';");
|
|
77
|
+
if (usesAltitude && !providers.length) add("ALT-SETUP-003", null, null, "Components from altitude-design are used, but nothing renders <AltitudeProvider>", "Render one AltitudeProvider around the whole app. It sets the scheme and hosts dialogs and toasts.");
|
|
78
|
+
if (providers.length > 1) findings.push({ ruleId: "ALT-SETUP-003", kind: "contextual-review", status: "review", file: null, line: null, observed: `<AltitudeProvider> is rendered in ${providers.length} places (${[...new Set(providers)].join(", ")})`, expected: "One AltitudeProvider around the app", repair: "Keep one around the app unless the others are deliberate (separate roots, previews).", verification: "altitude explain ALT-SETUP-003" });
|
|
79
|
+
return { files: sources.length, findings };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// scripts/altitude-cli.mjs
|
|
8
83
|
var sha = (value2) => createHash("sha256").update(value2).digest("hex");
|
|
9
84
|
var args = process.argv.slice(2);
|
|
10
85
|
var command = args.shift() || "help";
|
|
@@ -17,9 +92,9 @@ var json = args.includes("--json");
|
|
|
17
92
|
var emit = (result) => console.log(JSON.stringify(result, null, 2));
|
|
18
93
|
function installed() {
|
|
19
94
|
const path = createRequire(resolve(cwd, "package.json")).resolve("altitude-design/package.json");
|
|
20
|
-
const pkg = JSON.parse(
|
|
95
|
+
const pkg = JSON.parse(readFileSync2(path, "utf8"));
|
|
21
96
|
const guidance = resolve(dirname(path), "dist/guidance");
|
|
22
|
-
const index = JSON.parse(
|
|
97
|
+
const index = JSON.parse(readFileSync2(resolve(guidance, "index.json"), "utf8"));
|
|
23
98
|
return { pkg, guidance, index };
|
|
24
99
|
}
|
|
25
100
|
function managed(text) {
|
|
@@ -31,15 +106,23 @@ function managed(text) {
|
|
|
31
106
|
if (!m || sha(m[3]) !== m[2]) throw new Error("The managed Altitude block was edited; preserve your edits and reconcile it manually.");
|
|
32
107
|
return { start: m.index, end: m.index + m[0].length, created: m[1] === "true", body: m[3] };
|
|
33
108
|
}
|
|
34
|
-
function block(
|
|
35
|
-
const path =
|
|
36
|
-
const
|
|
37
|
-
|
|
109
|
+
function block({ pkg, guidance, index }, created) {
|
|
110
|
+
const path = relative2(cwd, guidance).replaceAll("\\", "/");
|
|
111
|
+
const declarations = relative2(cwd, resolve(guidance, "..", "types")).replaceAll("\\", "/") + "/";
|
|
112
|
+
const groups = /* @__PURE__ */ new Map();
|
|
113
|
+
for (const c of index.components || []) groups.set(c.group, [...groups.get(c.group) || [], c.name]);
|
|
114
|
+
const components = groups.size ? `Component guides: ${path}/components/<Name>/README.md. Read one before using that component.
|
|
115
|
+
` + [...groups].map(([g, names]) => `${g}|${names.join(",")}`).join("\n") + "\n" : "";
|
|
116
|
+
const body = `## Altitude framework (${pkg.version})
|
|
117
|
+
Altitude postdates your training data, and its props only look like shadcn's. Prefer these installed docs to what you remember, and check a component's props in its declaration under ${declarations} before using them.
|
|
118
|
+
For UI tasks using altitude-design, first read ${path}/README.md, then only the matching pattern document. Read ${path}/visual/README.md before composing a screen and ${path}/visual/Charts.md before any chart, stat or meter.
|
|
119
|
+
On every screen: one AltitudeProvider around the app and altitude-design/altitude.css imported once; inputs sunk and buttons raised, never the reverse; one primary action per view; no amount carried by height (charts are flat ink on a ground line: no shadows, 3D, pie or donut); status never colour alone (a word and an icon too); colours, shadows and radii from the tokens, never hard-coded.
|
|
38
120
|
Discover tasks with npm exec -- altitude patterns "your intent"; explain a rule with npm exec -- altitude explain RULE-ID.
|
|
39
|
-
|
|
121
|
+
Record applicable states and application-owned persistence, permissions and navigation.
|
|
122
|
+
After changing UI, run npm exec -- altitude check and fix its errors; it reports Altitude's rules that are certain from source (hard-coded colours, the stylesheet and provider, pie charts, OK and Submit labels) and lists likely problems for review.
|
|
40
123
|
Run the pattern checks and application service fixtures; distinguish deterministic defects, integration obligations and contextual review.
|
|
41
124
|
Use npm exec -- altitude doctor --json after package upgrades, and rerun npm exec -- altitude init to refresh this versioned pointer.
|
|
42
|
-
|
|
125
|
+
` + components;
|
|
43
126
|
return `
|
|
44
127
|
<!-- altitude:start created=${created} hash=${sha(body)} -->
|
|
45
128
|
${body}<!-- altitude:end -->
|
|
@@ -47,7 +130,7 @@ ${body}<!-- altitude:end -->
|
|
|
47
130
|
}
|
|
48
131
|
function instructionPath(client) {
|
|
49
132
|
const override = resolve(cwd, "AGENTS.override.md");
|
|
50
|
-
if (client === "codex" && existsSync(override) &&
|
|
133
|
+
if (client === "codex" && existsSync(override) && readFileSync2(override, "utf8").trim()) return override;
|
|
51
134
|
return resolve(cwd, client === "codex" ? "AGENTS.md" : "CLAUDE.md");
|
|
52
135
|
}
|
|
53
136
|
function isSymlink(path) {
|
|
@@ -67,9 +150,9 @@ function init(context) {
|
|
|
67
150
|
const targets = clients.flatMap((client2) => (remove && client2 === "codex" ? [resolve(cwd, "AGENTS.md"), resolve(cwd, "AGENTS.override.md")] : [instructionPath(client2)]).map((path) => ({ client: client2, path })));
|
|
68
151
|
const changes = targets.map(({ client: c, path }) => {
|
|
69
152
|
if (isSymlink(path)) throw new Error(`Refusing instruction symlink: ${path}`);
|
|
70
|
-
const existed = existsSync(path), before = existed ?
|
|
153
|
+
const existed = existsSync(path), before = existed ? readFileSync2(path, "utf8") : "";
|
|
71
154
|
const old = managed(before);
|
|
72
|
-
const replacement = remove ? "" : block(context
|
|
155
|
+
const replacement = remove ? "" : block(context, old?.created ?? !existed);
|
|
73
156
|
const after = old ? before.slice(0, old.start) + replacement + before.slice(old.end) : before + (remove ? "" : replacement);
|
|
74
157
|
const deleteFile = remove && old?.created && after === "";
|
|
75
158
|
return { client: c, path, before, after, action: deleteFile ? "delete" : before === after ? "unchanged" : existed ? "update" : "create" };
|
|
@@ -86,35 +169,52 @@ function finding(ruleId, kind, status, observed, expected, repair, verification)
|
|
|
86
169
|
function doctor(context) {
|
|
87
170
|
const findings = [];
|
|
88
171
|
if (context.pkg.version !== context.index.version) findings.push(finding("ALT-SETUP-001", "deterministic", "fail", `Package ${context.pkg.version}; guidance ${context.index.version}`, "Matching versions", "Reinstall a coherent package.", "altitude doctor --json"));
|
|
89
|
-
const manifest = JSON.parse(
|
|
172
|
+
const manifest = JSON.parse(readFileSync2(resolve(context.guidance, "manifest.json"), "utf8"));
|
|
90
173
|
if (manifest.version !== context.pkg.version) findings.push(finding("ALT-SETUP-001", "deterministic", "fail", `Manifest ${manifest.version}`, "Manifest matches installed package", "Reinstall the package.", "altitude doctor --json"));
|
|
91
174
|
for (const [file, digest] of Object.entries(manifest.files)) {
|
|
92
175
|
const path = resolve(context.guidance, file);
|
|
93
|
-
if (!path.startsWith(context.guidance + "/") || !existsSync(path) || sha(
|
|
176
|
+
if (!path.startsWith(context.guidance + "/") || !existsSync(path) || sha(readFileSync2(path)) !== digest) findings.push(finding("ALT-SETUP-001", "deterministic", "fail", `Missing or modified guidance: ${file}`, "Unmodified version-matched guidance", "Reinstall the package.", "altitude doctor --json"));
|
|
94
177
|
}
|
|
95
178
|
for (const client of ["codex", "claude"]) {
|
|
96
179
|
const path = instructionPath(client), file = basename(path);
|
|
97
180
|
try {
|
|
98
|
-
const text = existsSync(path) ?
|
|
181
|
+
const text = existsSync(path) ? readFileSync2(path, "utf8") : "";
|
|
99
182
|
const current = managed(text);
|
|
100
|
-
if (current && text.slice(current.start, current.end) !== block(context
|
|
183
|
+
if (current && text.slice(current.start, current.end) !== block(context, current.created)) findings.push(finding("ALT-SETUP-001", "deterministic", "fail", `${file} points to different guidance`, "Setup matches installed version and location", `altitude init --client ${client} --apply`, "altitude doctor --json"));
|
|
101
184
|
else findings.push(finding("ALT-SETUP-001", "deterministic", current ? "pass" : "not-configured", `${client}: ${current ? "managed pointer matches" : "no managed pointer"}`, "Explicit setup when agent integration is desired", `altitude init --client ${client}`, "Restart client and inspect loaded instructions."));
|
|
102
185
|
} catch (e) {
|
|
103
186
|
findings.push(finding("ALT-SETUP-001", "deterministic", "fail", `${file}: ${e.message}`, "One intact managed block", "Reconcile edited/duplicate markers manually.", "altitude doctor --json"));
|
|
104
187
|
}
|
|
105
188
|
}
|
|
106
|
-
const rules = JSON.parse(
|
|
189
|
+
const rules = JSON.parse(readFileSync2(resolve(context.guidance, "rules.json"), "utf8")).rules;
|
|
107
190
|
for (const r of rules.filter((r2) => r2.diagnosticClass !== "deterministic")) findings.push(finding(r.id, r.diagnosticClass, "unverified", "CLI has not exercised application behavior or conducted human review.", r.title, r.repair, r.verification));
|
|
108
191
|
const ok = !findings.some((f) => f.kind === "deterministic" && f.status === "fail");
|
|
109
192
|
emit({ command: "doctor", packageVersion: context.pkg.version, guidanceVersion: context.index.version, ok, scope: "Installation and managed pointers only; not accessibility or usability certification.", findings });
|
|
110
193
|
if (!ok) process.exitCode = 1;
|
|
111
194
|
}
|
|
195
|
+
function check(context) {
|
|
196
|
+
const target = resolve(cwd, args.find((v, i) => !v.startsWith("--") && args[i - 1] !== "--cwd") || (existsSync(resolve(cwd, "src")) ? "src" : "."));
|
|
197
|
+
if (!existsSync(target)) throw new Error(`No such directory: ${target}`);
|
|
198
|
+
const tokensCss = resolve(context.guidance, "..", "tokens.css");
|
|
199
|
+
const tokenNames = new Set(existsSync(tokensCss) ? [...readFileSync2(tokensCss, "utf8").matchAll(/--([\w-]+)\s*:/g)].map((m) => m[1]) : []);
|
|
200
|
+
const { files: files2, findings } = scan(target, { tokenNames });
|
|
201
|
+
const ok = !findings.some((f) => f.kind === "deterministic");
|
|
202
|
+
const scope = "Altitude rules certain from source text only. Not checked: anything that needs the page rendered (contrast, overflow, focus) or a person (accessibility, usability).";
|
|
203
|
+
if (json) emit({ command: "check", version: context.pkg.version, target: relative2(cwd, target) || ".", files: files2, ok, scope, findings });
|
|
204
|
+
else {
|
|
205
|
+
for (const f of findings) console.log(`${f.ruleId} ${f.kind === "deterministic" ? "error " : "review"} ${f.file ? `${f.file}${f.line ? ":" + f.line : ""}` : "(project)"} ${f.observed}
|
|
206
|
+
${f.repair}`);
|
|
207
|
+
console.log(`${findings.filter((f) => f.kind === "deterministic").length} errors, ${findings.filter((f) => f.kind !== "deterministic").length} to review in ${files2} files. ${scope} Explain a rule: npm exec -- altitude explain RULE-ID`);
|
|
208
|
+
}
|
|
209
|
+
if (!ok) process.exitCode = 1;
|
|
210
|
+
}
|
|
112
211
|
try {
|
|
113
|
-
if (command === "help" || command === "--help") console.log("altitude init --client codex|claude|all [--apply] [--remove]\naltitude patterns [intent] [--json]\naltitude explain RULE-ID [--json]\naltitude doctor [--json]\nAll commands resolve the installed package from --cwd PATH (default current directory).");
|
|
212
|
+
if (command === "help" || command === "--help") console.log("altitude init --client codex|claude|all [--apply] [--remove]\naltitude patterns [intent] [--json]\naltitude explain RULE-ID [--json]\naltitude doctor [--json]\naltitude check [dir] [--json]\nAll commands resolve the installed package from --cwd PATH (default current directory).");
|
|
114
213
|
else {
|
|
115
214
|
const context = installed();
|
|
116
215
|
if (command === "init") init(context);
|
|
117
216
|
else if (command === "doctor") doctor(context);
|
|
217
|
+
else if (command === "check") check(context);
|
|
118
218
|
else if (command === "patterns") {
|
|
119
219
|
const words = args.filter((v, i) => !v.startsWith("--") && args[i - 1] !== "--cwd").join(" ").toLowerCase().split(/\W+/).filter(Boolean);
|
|
120
220
|
const patterns = context.index.patterns.map((p) => ({ ...p, score: words.reduce((n, w) => n + (p.intents.some((i) => i === w || i.startsWith(w)) ? 1 : 0), 0) })).filter((p) => !words.length || p.score).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
@@ -123,7 +223,7 @@ try {
|
|
|
123
223
|
${resolve(context.guidance, p.document)}`).join("\n") : "No pattern matched. Run altitude patterns to browse the catalog.");
|
|
124
224
|
} else if (command === "explain") {
|
|
125
225
|
const id = args[0];
|
|
126
|
-
const rule = JSON.parse(
|
|
226
|
+
const rule = JSON.parse(readFileSync2(resolve(context.guidance, "rules.json"), "utf8")).rules.find((r) => r.id === id);
|
|
127
227
|
if (!rule) throw new Error(`Unknown rule: ${id}`);
|
|
128
228
|
emit({ version: context.pkg.version, ...rule });
|
|
129
229
|
} else throw new Error(`Unknown command: ${command}`);
|
package/dist/guidance/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Start from the person's task. Read only the relevant pattern below, its linked rules, and the public declarations. The installed copy matches the package version; `altitude doctor --json` checks that connection. Guidance supports judgment; it does not certify an application.
|
|
4
4
|
|
|
5
|
+
Every screen also follows the visual rules. Read the [design guide](visual/README.md) before composing one, [Elevation](visual/Elevation.md) for what height means, [Charts](visual/Charts.md) before drawing any chart, stat or meter, and [Motion](visual/Motion.md) before animating. Each component has its own guide (what it's for, how it uses height, what you provide); read it before using the component: for example [Button](components/Button/README.md) or [Select](components/Select/README.md). The `components` list in `index.json` has every component with its group, a one-line summary and its guide.
|
|
6
|
+
|
|
5
7
|
Composing a new screen? Choose its frame from the task with [screen layout](patterns/screen-layout.md) first: name the task, the one thing that leads and the one primary action. The [app skeleton](app-skeleton.md) is one of its frames, for working through many similar records: shell, page header, a filtered and paged table, a record in a sheet, and settings, composed correctly.
|
|
6
8
|
|
|
7
9
|
| Intent | Focused contract |
|
|
@@ -29,4 +31,6 @@ Use `altitude patterns "edit settings" --json` to discover contracts and `altitu
|
|
|
29
31
|
|
|
30
32
|
Compose existing primitives and small workflow hooks from the main `altitude-design` export inside one `AltitudeProvider`. Import a package stylesheet. The application supplies routes, business validation and services. Verify delayed operations, rejection, retry, cancellation and stale responses where applicable; include keyboard and manual assistive-technology review. Automated checks cannot establish complete accessibility or task suitability.
|
|
31
33
|
|
|
34
|
+
After changing UI, run `npm exec -- altitude check` (or `altitude check src --json`). It reports Altitude's rules that are certain from source text as errors (hard-coded colours instead of tokens, a missing stylesheet or `AltitudeProvider`, pie and donut charts, buttons labelled OK or Submit) and likely problems for review (several primary buttons in one file, elevation overrides on controls). Fix the errors; decide on the reviews. Each finding names a rule `altitude explain` describes. It doesn't check anything that needs the page rendered or a person.
|
|
35
|
+
|
|
32
36
|
For explicit agent setup, preview `npm exec -- altitude init --client codex` (or `claude`), then use `--apply`. Full setup and removal instructions are shipped as `agent-setup.md`, generated from the repository’s `docs/framework/AGENT-SETUP.md`. No installation hook writes project instructions.
|
|
@@ -9,14 +9,15 @@ npm exec -- altitude init --client claude --apply
|
|
|
9
9
|
npm exec -- altitude patterns "edit settings" --json
|
|
10
10
|
npm exec -- altitude explain ALT-EDIT-001
|
|
11
11
|
npm exec -- altitude doctor --json
|
|
12
|
+
npm exec -- altitude check --json
|
|
12
13
|
npm exec -- altitude init --client codex --remove
|
|
13
14
|
npm exec -- altitude init --client codex --remove --apply
|
|
14
15
|
```
|
|
15
16
|
|
|
16
17
|
`init` defaults to a preview, including the exact before/after text. `--apply` applies that preview. `--client all` targets both clients. Repeating setup makes no change. Removal removes only the managed block, restoring surrounding bytes; a file created by setup is removed only if nothing else remains. User-edited managed blocks, malformed markers and symlinks are rejected for manual reconciliation. Existing instructions outside the block are preserved. Run setup again after upgrading the installed package. Use `--cwd PATH` to target an explicit project directory. Resolution uses that directory's installed `altitude-design/package.json`, never the version fetched by a different CLI invocation.
|
|
17
18
|
|
|
18
|
-
The Codex adapter writes a compact routing block to project `AGENTS.md` (or an existing nonempty `AGENTS.override.md`, which Codex gives precedence); the Claude Code adapter writes it to project `CLAUDE.md`. Neither assumes instruction files under `node_modules` are automatically discovered. The routing block points at version-matched local focused documents rather than importing the complete manual. Restart the agent session after setup. Confirm Codex reports the project instruction file and Claude Code's `/context` lists it under Memory files; then ask an ordinary product question and check that the relevant pattern is read. Instructions are context, not enforcement, and broader or more specific instructions may affect discovery.
|
|
19
|
+
The Codex adapter writes a compact routing block to project `AGENTS.md` (or an existing nonempty `AGENTS.override.md`, which Codex gives precedence); the Claude Code adapter writes it to project `CLAUDE.md`. Neither assumes instruction files under `node_modules` are automatically discovered. The routing block points at version-matched local focused documents rather than importing the complete manual. Because it's read on every turn, it carries only what an agent can't work out for itself: a reminder to prefer the installed docs and declarations to its memory, the rules that hold on every screen, and a compact index of every component (by group, with the path pattern of each component's guide), read on demand. The `agent-setup` check keeps it under 8 KB and verifies that every path it names is installed. Restart the agent session after setup. Confirm Codex reports the project instruction file and Claude Code's `/context` lists it under Memory files; then ask an ordinary product question and check that the relevant pattern is read. Instructions are context, not enforcement, and broader or more specific instructions may affect discovery.
|
|
19
20
|
|
|
20
21
|
Mechanisms verified against official documentation on 2026-10-23: [Codex AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md) describes root-to-working-directory instruction discovery, override precedence and size limits; [Claude Code memory](https://code.claude.com/docs/en/memory) documents project `CLAUDE.md` loading and `/context` inspection. The adapter tests verify files and round trips, not successful behavior of every client release. Claude Code also documents AGENTS.md support; CLAUDE.md remains the explicit adapter target.
|
|
21
22
|
|
|
22
|
-
`doctor` reports stable rule IDs, observed evidence, expected behavior, repairs and verification. It returns a failing exit code only for deterministic installation/setup defects; service integration and contextual obligations remain visibly unverified. It does not inspect an application's service behavior or certify accessibility. Guidance remains usable offline through `altitude-design/guidance`, `altitude-design/guidance/*`, and physical `node_modules/altitude-design/dist/guidance` files. Tooling stays outside browser imports.
|
|
23
|
+
`doctor` reports stable rule IDs, observed evidence, expected behavior, repairs and verification. It returns a failing exit code only for deterministic installation/setup defects; service integration and contextual obligations remain visibly unverified. It does not inspect an application's service behavior or certify accessibility. `check [dir]` (default `src`, else the project) scans an application's own source for Altitude's rules that are certain from text: hard-coded colours instead of tokens (`ALT-TOKEN-001`), the stylesheet and `AltitudeProvider` (`ALT-SETUP-002`, `ALT-SETUP-003`), pie and donut charts (`ALT-CHART-001`) and buttons labelled OK or Submit (`ALT-COPY-001`). Those fail the run. Several primary buttons in one file (`ALT-PRIMARY-001`) and elevation overrides on controls (`ALT-VISUAL-004`) are listed for review and never fail. A finding a team keeps is marked `altitude-ignore: <ID> <reason>` on its line or the line above. It reads text only: nothing that needs the page rendered or a person is checked. Guidance remains usable offline through `altitude-design/guidance`, `altitude-design/guidance/*`, and physical `node_modules/altitude-design/dist/guidance` files. Tooling stays outside browser imports.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# AILabel
|
|
2
|
+
|
|
3
|
+
Says when content was generated or changed by AI, and explains how when asked: for trust, and under transparency rules such as the EU AI Act. Carbon for AI made its label the entry point to explainability; this follows that, without the gradient.
|
|
4
|
+
|
|
5
|
+
**Elevation: a label is part of the record.** It's a flush `Badge` plate, a small static icon and a word: "AI", or **"Drafted with AI"** once a person has reviewed it.
|
|
6
|
+
- **No gradient, no glow, no shimmer.** A glow is light from below, and the light never moves. A gradient rim is also easily mistaken for focus.
|
|
7
|
+
- **It acts**, so it lifts one level when pointed at, like any plate that acts.
|
|
8
|
+
- **The explanation** is a `Popover` at level 3, "About this content": the model, when it was generated (`RelativeTime`), confidence in words, who reviewed or edited it, and the sources it was based on.
|
|
9
|
+
- **Provenance stays.** Accepting AI content doesn't remove its label; the plate changes to "Drafted with AI" and the explanation says who reviewed it.
|
|
10
|
+
|
|
11
|
+
**Accessibility.** A button with `aria-haspopup="dialog"` opening a non-modal popover. Its name says what it marks ("Summary generated with AI. Show details", or "drafted with AI and reviewed"). Tab, Enter and Escape. Under forced colours the plate has a border, and the icon and word carry the meaning. Under `contrast="more"` the plate's border is `line-control`.
|
|
12
|
+
|
|
13
|
+
**Props:** `subject` (what it marks, default "Content"); `model`; `generatedAt`; `sources` (`{ title, url? }[]`); `confidence`; `reviewedBy`; `editedBy`; `children` (anything else about how it was made).
|
|
14
|
+
|
|
15
|
+
**You provide:** the provenance. Place the label beside what it marks, such as a heading or a field label.
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
<h3>Summary <AILabel subject="Summary" model="Opus 5.5" generatedAt={summary.at}
|
|
19
|
+
sources={summary.sources} reviewedBy={summary.reviewer} confidence="Medium: two sources disagreed" /></h3>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- Do keep the label once a person accepts the content.
|
|
23
|
+
- Do say confidence in words.
|
|
24
|
+
- Don't decorate AI content with gradients, glows or shimmer.
|
|
25
|
+
- Don't use the label as the only place AI involvement is disclosed when your rules require more.
|
|
26
|
+
|
|
27
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
`AILabel` as first used in its preview:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { AILabel } from 'altitude-design';
|
|
34
|
+
|
|
35
|
+
<AILabel subject="Summary" model="Opus 5.5" generatedAt={new Date(Date.now() - 4 * 60000)} confidence="Medium: two sources disagreed about the renewal date"
|
|
36
|
+
sources={[{ title: 'Call with Jamie Lee, 28 Oct', url: '#/calls/28' }, { title: 'Contract v3.pdf', url: '#/files/contract' }]} />
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The full preview, as rendered in Storybook and checked in CI (`col`, `al`, `ttl` are the preview page's own layout classes, not Altitude's):
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { AILabel } from 'altitude-design';
|
|
43
|
+
|
|
44
|
+
export default function App() {
|
|
45
|
+
return (
|
|
46
|
+
<div className="col al">
|
|
47
|
+
<div>
|
|
48
|
+
<h3 className="ttl">Summary <AILabel subject="Summary" model="Opus 5.5" generatedAt={new Date(Date.now() - 4 * 60000)} confidence="Medium: two sources disagreed about the renewal date"
|
|
49
|
+
sources={[{ title: 'Call with Jamie Lee, 28 Oct', url: '#/calls/28' }, { title: 'Contract v3.pdf', url: '#/files/contract' }]} /></h3>
|
|
50
|
+
<p>Jamie wants to move to annual billing in November and asked whether the 30-day refund applies to the remaining monthly period.</p>
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
<h3 className="ttl">Reply <AILabel subject="Reply" model="Opus 5.5" generatedAt={new Date(Date.now() - 3 * 3600000)} reviewedBy="Maya Chen" editedBy="Maya Chen" /></h3>
|
|
54
|
+
<p>Hi Jamie, thanks for getting in touch. Your full refund of €240 is on its way.</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
<!-- /example -->
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
Reveals sections one at a time. Closed items are **lids** lying flush on the page; opening one lifts its lid into a raised card and opens the **compartment** underneath, where the content sits.
|
|
4
|
+
|
|
5
|
+
Closed rows sit on the ground divided by `line` hairlines. Hovering one raises a ghost of its lid (a faint raised tint with a hairline), so you can see what will open. Opening raises the item to level 2 (`surface-raised`, `elev-2`), opens a gap around it and hides the neighbouring dividers. Inside, the content sits in a compartment: an opening sunk into the lid (`surface-well`) with its back wall showing just below the rim, the same camera as an empty `Input`. The panel grows on `ease-glide`, the content emerges 60ms later, and the chevron turns on `ease-settle`. Closed panels are inert to keyboard and assistive tech.
|
|
6
|
+
|
|
7
|
+
**You provide:** `items` (`value`, `title`, `content`), `type` (`single` closes the others; `multiple` doesn't), `defaultValue` as an array of open values.
|
|
8
|
+
|
|
9
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
10
|
+
## Example
|
|
11
|
+
|
|
12
|
+
`Accordion` as first used in its preview:
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Accordion } from 'altitude-design';
|
|
16
|
+
|
|
17
|
+
<Accordion items={items} defaultValue={['b']} />
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The full preview, as rendered in Storybook and checked in CI (`wrap` is the preview page's own layout class, not Altitude's):
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { Accordion } from 'altitude-design';
|
|
24
|
+
|
|
25
|
+
const items = [
|
|
26
|
+
{ value: 'a', title: 'What does elevation mean here?', content: 'Height says how much something matters right now and what you can do with it: raised things are pushed, sunk things are filled.' },
|
|
27
|
+
{ value: 'b', title: 'Can I bring my own colour?', content: 'Pass any hex as accent to AltitudeProvider. The accent text and soft tints are derived to stay legible on every surface.' },
|
|
28
|
+
{ value: 'c', title: 'Does it respect reduced motion?', content: 'Yes. Surfaces still change level, tint and shadow, but nothing travels or scales.' },
|
|
29
|
+
];
|
|
30
|
+
export default function App() { return <div className="wrap"><Accordion items={items} defaultValue={['b']} /></div>; }
|
|
31
|
+
```
|
|
32
|
+
<!-- /example -->
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# AgentFleet
|
|
2
|
+
|
|
3
|
+
Every agent working on one job, as a roster: grouped by what they need from you (needs you, working, done), each row saying the agent's state in words, what it's doing in one line, how long it has waited or run, where it is in its plan and what it has spent, with warnings for trouble that's coming (two agents changing one file, an agent making no progress). For supervising several agents at once; see [the research](https://github.com/richlaconte/altitude/blob/v0.4.1/docs/research/agent-orchestration/README.md).
|
|
4
|
+
|
|
5
|
+
**Elevation: a record of agents, with live rows.**
|
|
6
|
+
- **Rows** are the record: flush at rest, lifting one level when pointed at because selecting one opens it. The open agent is selected, as a table's row is: `accent-soft`, raised one level, `aria-current`.
|
|
7
|
+
- **Working** isn't pending (nothing you sent is in flight), so it never hovers: a plate with a steady accent lamp says "Working". No spinner.
|
|
8
|
+
- **Needs you** ("Needs approval", "Has a question", "Ready for review") is a caution plate with a lamp, and "waiting 6 min". It doesn't rise: a row can't lift into a card inside a list, and `AgentRequests` holds the raised card.
|
|
9
|
+
- **Stalled** ("No progress for 7 min", "Retried the same step 3 times") is a caution plate and a clock mark, in the working group: a suspicion, not a request.
|
|
10
|
+
- **Blocked** says on what ("Waiting on Billing API"); **queued** is a neutral plate. **Failed** is a critical plate and leads with the agents that need you, since nothing happens until someone retries or stops it.
|
|
11
|
+
- **Done** is stamped: a check, the name in muted ink, "took 9 min". The done group folds away.
|
|
12
|
+
- **A conflict** is a flush `caution-soft` strip under the row naming the file and the other agent: never colour alone.
|
|
13
|
+
- **Agents an agent started** hang under it in a 1px `line-control` bracket, as `TraceParallel` draws subagents.
|
|
14
|
+
|
|
15
|
+
**Accessibility.** A region named by `aria-label` ("Agents"). Each group is a list named with its count ("Needs you, 2"); a collapsible group's heading is a button with `aria-expanded`. Each row is a button whose name says everything in order: "Billing API, waiting 6 min, Wants to run the migration on staging, Needs approval, Step 3 of 4, spent $0.38". Up and Down move between rows across groups; Home and End go to the first and last. An agent that starts needing you (or fails) is announced politely once; starting, progress, stalling and finishing aren't.
|
|
16
|
+
|
|
17
|
+
**Parts:** `AgentFleet` (`aria-label`, `now`, `summary`); `AgentFleetGroup` (`label`, `count`, `collapsible`, `defaultOpen`, `empty`); `AgentFleetItem` (`name`, `state`: `'queued' | 'working' | 'needs-input' | 'blocked' | 'stalled' | 'failed' | 'done' | 'stopped'`, `needs`: `'approval' | 'question' | 'review'`, `activity`, `since`, `startedAt`, `endedAt`, `step`, `blockedBy`, `conflict`, `stalledReason`, `cost`, `output`, `selected`, `onSelect`, `children`). Helpers: `groupAgents(agents, get)` sorts into needs you (longest wait first), working (stalled first) and finished (newest first); `summarizeFleet(states)` writes "2 need you · 3 working · 4 done"; `formatSpan(ms)` writes "6 min".
|
|
18
|
+
|
|
19
|
+
**You provide:** the agents and their states, a one-line `activity` for each (the agent's last status, or a summary from a small model), when each entered its state, and what selecting does (open it beside the fleet). Detecting a stall or a conflict is yours too: pass `state="stalled"` when an agent has made no progress for a while or repeats a step, and `conflict` when two agents change the same file.
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
const g = groupAgents(agents, (a) => a);
|
|
23
|
+
const row = (a) => <AgentFleetItem key={a.id} {...a} selected={a.id === open} onSelect={() => setOpen(a.id)} />;
|
|
24
|
+
<AgentFleet summary={summarizeFleet(agents.map((a) => a.state))}>
|
|
25
|
+
<AgentFleetGroup label="Needs you" empty="Nothing needs you.">{g.needsYou.map(row)}</AgentFleetGroup>
|
|
26
|
+
<AgentFleetGroup label="Working">{g.working.map(row)}</AgentFleetGroup>
|
|
27
|
+
<AgentFleetGroup label="Done" collapsible defaultOpen={false}>{g.finished.map(row)}</AgentFleetGroup>
|
|
28
|
+
</AgentFleet>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- Do sort by need: the agent waiting longest on you goes first.
|
|
32
|
+
- Do say every state in words; the lamp and mark only repeat it.
|
|
33
|
+
- Don't show a spinner, a ticking counter or make a working row hover.
|
|
34
|
+
- Don't lay agents out as a grid of equal cards: the one that needs you gets lost among the ones that don't.
|
|
35
|
+
|
|
36
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
37
|
+
## Example
|
|
38
|
+
|
|
39
|
+
`AgentFleet` as first used in its preview:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { AgentFleet, AgentFleetGroup, summarizeFleet } from 'altitude-design';
|
|
43
|
+
|
|
44
|
+
<AgentFleet now={now} summary={summarizeFleet(seed.map((a) => a.state))}>
|
|
45
|
+
<AgentFleetGroup label="Needs you" empty="Nothing needs you.">{g.needsYou.map(row)}</AgentFleetGroup>
|
|
46
|
+
<AgentFleetGroup label="Working">{g.working.map(row)}</AgentFleetGroup>
|
|
47
|
+
<AgentFleetGroup label="Done" collapsible>{g.finished.map(row)}</AgentFleetGroup>
|
|
48
|
+
</AgentFleet>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The full preview, as rendered in Storybook and checked in CI (`af` is the preview page's own layout class, not Altitude's):
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
import * as React from 'react';
|
|
55
|
+
import { AgentFleet, AgentFleetGroup, AgentFleetItem, groupAgents, summarizeFleet } from 'altitude-design';
|
|
56
|
+
|
|
57
|
+
const now = Date.UTC(2026, 9, 31, 14, 20), min = 60000;
|
|
58
|
+
const seed = [
|
|
59
|
+
{ id: 'api', name: 'Billing API', state: 'needs-input', needs: 'approval', activity: 'Wants to run the migration on staging', since: now - 6 * min, startedAt: now - 17 * min, step: { current: 3, total: 4 }, cost: '$0.38' },
|
|
60
|
+
{ id: 'qa', name: 'QA', state: 'failed', activity: '2 of 14 checkout tests failed', since: now - 2 * min, startedAt: now - 5 * min, cost: '$0.11' },
|
|
61
|
+
{ id: 'docs', name: 'Docs', state: 'stalled', stalledReason: 'Retried the same step 3 times', activity: 'Link check timed out on /enterprise; retrying', since: now - 5 * min, startedAt: now - 17 * min, step: { current: 3, total: 3 }, cost: '$0.31' },
|
|
62
|
+
{ id: 'pricing', name: 'Pricing page', state: 'working', activity: 'Writing the annual toggle in Pricing.tsx', startedAt: now - 17 * min, step: { current: 2, total: 4 }, cost: '$0.41', conflict: { file: 'src/content/pricing.ts', with: 'Docs' } },
|
|
63
|
+
{ id: 'changelog', name: 'Changelog', state: 'blocked', blockedBy: 'Pricing page' },
|
|
64
|
+
{ id: 'emails', name: 'Renewal emails', state: 'done', activity: 'Scheduled for Tuesday 9:00 to 1,240 customers', startedAt: now - 17 * min, endedAt: now - 3 * min, cost: '$0.29' },
|
|
65
|
+
];
|
|
66
|
+
export default function App() {
|
|
67
|
+
const [open, setOpen] = React.useState('pricing');
|
|
68
|
+
const g = groupAgents(seed, (a) => a);
|
|
69
|
+
const row = (a) => <AgentFleetItem key={a.id} {...a} selected={a.id === open} onSelect={() => setOpen(a.id)} />;
|
|
70
|
+
return (
|
|
71
|
+
<div className="af">
|
|
72
|
+
<AgentFleet now={now} summary={summarizeFleet(seed.map((a) => a.state))}>
|
|
73
|
+
<AgentFleetGroup label="Needs you" empty="Nothing needs you.">{g.needsYou.map(row)}</AgentFleetGroup>
|
|
74
|
+
<AgentFleetGroup label="Working">{g.working.map(row)}</AgentFleetGroup>
|
|
75
|
+
<AgentFleetGroup label="Done" collapsible>{g.finished.map(row)}</AgentFleetGroup>
|
|
76
|
+
</AgentFleet>
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
<!-- /example -->
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# AgentLanes
|
|
2
|
+
|
|
3
|
+
Where each agent's time went, one lane per agent on a shared clock: working, waiting on you, waiting on another agent, failed. Its takeaway leads with how long agents have waited on you, the bottleneck a fleet of agents hides: once several run, the person is usually the slowest part.
|
|
4
|
+
|
|
5
|
+
**Elevation: a chart, so nothing has height** (see [Charts](../../visual/Charts.md)).
|
|
6
|
+
- **Stretches** are equal-height flat bars lying on each lane's ground line: working is `data-1`; waiting on you is `caution` hatched at 45°; waiting on another agent is a 1px dashed outline; failed is `critical`. A gap is time the agent wasn't running.
|
|
7
|
+
- **Every fill is named** in the key, in words, and each bar's title says its agent, state and span.
|
|
8
|
+
- **"Now"** is a 1px `line-control` rule at the end of the record; the axis says "31 min ago", "15 min ago", "Now".
|
|
9
|
+
- **The selected lane** is its name in `accent-ink` and 650, with `aria-pressed`; lanes never lift.
|
|
10
|
+
- **Forced colours**: working solid, waiting hatched, blocked dashed, failed cross-hatched with a thick border.
|
|
11
|
+
|
|
12
|
+
**Accessibility.** A `figure` captioned by its takeaway. The lane names are buttons when `onSelect` is set; the bars and axis are hidden from assistive technology and a table gives the same data (agent; time working, waiting on you, waiting on another agent, failed).
|
|
13
|
+
|
|
14
|
+
**Props:** `lanes` (`{ id, name, segments: { from, to?, state }[] }[]`; a segment without `to` runs to now), `start` (default the earliest segment), `now`, `takeaway` (default "Agents have waited on you for 15 min in all; the longest wait was Pricing page, 5 min."), `aria-label`, `selected`, `onSelect`. Time spent failed counts as waiting on you in the takeaway, since a failed agent waits for a retry.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
<AgentLanes start={job.startedAt} lanes={agents.map((a) => ({ id: a.id, name: a.name, segments: a.history }))} selected={open} onSelect={setOpen} />
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- Do keep it below the roster: it explains the past, the roster and requests are for now.
|
|
21
|
+
- Don't draw dependencies as arrows between lanes; say them on the waiting agent ("Waiting on Billing API").
|
|
22
|
+
|
|
23
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
24
|
+
## Example
|
|
25
|
+
|
|
26
|
+
`AgentLanes` as first used in its preview:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { AgentLanes } from 'altitude-design';
|
|
30
|
+
|
|
31
|
+
<AgentLanes lanes={lanes} start={t0} now={m(31)} selected={sel} onSelect={setSel} />
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The full preview, as rendered in Storybook and checked in CI (`al` is the preview page's own layout class, not Altitude's):
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import * as React from 'react';
|
|
38
|
+
import { AgentLanes } from 'altitude-design';
|
|
39
|
+
|
|
40
|
+
const t0 = Date.UTC(2026, 9, 31, 14, 0), m = (n) => t0 + n * 60000;
|
|
41
|
+
const lanes = [
|
|
42
|
+
{ id: 'pricing', name: 'Pricing page', segments: [{ from: m(3), to: m(20), state: 'working' }, { from: m(20), to: m(25), state: 'waiting' }] },
|
|
43
|
+
{ id: 'api', name: 'Billing API', segments: [{ from: m(3), to: m(7), state: 'working' }, { from: m(7), to: m(10), state: 'waiting' }, { from: m(10), to: m(19), state: 'working' }] },
|
|
44
|
+
{ id: 'emails', name: 'Renewal emails', segments: [{ from: m(3), to: m(8), state: 'working' }, { from: m(8), to: m(13), state: 'waiting' }, { from: m(13), to: m(25), state: 'working' }] },
|
|
45
|
+
{ id: 'qa', name: 'QA', segments: [{ from: m(3), to: m(19), state: 'blocked' }, { from: m(19), to: m(21), state: 'working' }, { from: m(21), to: m(23), state: 'failed' }, { from: m(23), to: m(29), state: 'working' }] },
|
|
46
|
+
{ id: 'changelog', name: 'Changelog', segments: [{ from: m(27), to: m(30), state: 'working' }] },
|
|
47
|
+
];
|
|
48
|
+
export default function App() {
|
|
49
|
+
const [sel, setSel] = React.useState('qa');
|
|
50
|
+
return <div className="al"><AgentLanes lanes={lanes} start={t0} now={m(31)} selected={sel} onSelect={setSel} /></div>;
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
<!-- /example -->
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# AgentRequests
|
|
2
|
+
|
|
3
|
+
Every decision your agents are waiting on, in one queue: approvals, questions, reviews and failures, oldest first, each answerable where it is. Beside an `AgentFleet`, it lets a person supervising several agents clear what's waiting in a row, without opening each agent.
|
|
4
|
+
|
|
5
|
+
**Elevation: status that asks for action.**
|
|
6
|
+
- **A request** is a raised card (level 1), because it asks for action; it arrives lifted and settles with the afterglow. It isn't pending: nothing has been dispatched until it's answered.
|
|
7
|
+
- **Its kind** is a plate in words: "Needs approval", "Has a question", "Ready for review" (caution, with a lamp) or "Couldn't finish" (critical). Who's asking and "waiting 6 min" sit beside it.
|
|
8
|
+
- **Its answers** are secondary and ghost buttons: the view keeps its one primary. Open is a ghost at the end.
|
|
9
|
+
- **Nothing waiting** is said flush with a check ("Nothing needs you"): an all-clear is settled, not an opening.
|
|
10
|
+
- **The count** beside the heading is the one part that floats, as any count that wants you does.
|
|
11
|
+
|
|
12
|
+
**Accessibility.** A section named by its heading ("Needs you"). Requests are an ordered list, longest wait first; each is an `article` named by its ask and described by its kind, agent and wait. Open is named for the agent ("Open Billing API"). A request that arrives is announced politely once ("Billing API: Run the migration on staging?").
|
|
13
|
+
|
|
14
|
+
**Parts:** `AgentRequests` (`title`, `now`, `empty`); `AgentRequest` (`agent`, `kind`: `'approval' | 'question' | 'review' | 'failed'`, `title`, `detail`, `since`, `actions`, `onOpen`).
|
|
15
|
+
|
|
16
|
+
**You provide:** the requests (give each a stable `key`), what each answer does, and removing a request once it's answered. For a question with many options or your own answer, or an approval with scopes or edits, use Open and answer in the agent's own `Question` or `Confirmation`.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<AgentRequests>
|
|
20
|
+
{waiting.map((r) => (
|
|
21
|
+
<AgentRequest key={r.id} agent={r.agent} kind={r.kind} since={r.since} title={r.title} detail={r.consequence} onOpen={() => open(r.agentId)}
|
|
22
|
+
actions={<><Button size="sm" variant="secondary" onClick={() => approve(r)}>Run migration</Button><Button size="sm" variant="ghost" onClick={() => reject(r)}>Don't run</Button></>} />
|
|
23
|
+
))}
|
|
24
|
+
</AgentRequests>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- Do say the consequence of an approval in `detail`.
|
|
28
|
+
- Don't offer "approve all": answer each with its consequence in view.
|
|
29
|
+
- Don't make an answer primary; the view's one primary stays where it is.
|
|
30
|
+
|
|
31
|
+
<!-- example: generated from build/previews.txt by build/previews.mjs. Edit the preview, not this section. -->
|
|
32
|
+
## Example
|
|
33
|
+
|
|
34
|
+
`AgentRequests` as first used in its preview:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { AgentRequest, AgentRequests, Button } from 'altitude-design';
|
|
38
|
+
|
|
39
|
+
<AgentRequests now={now}>
|
|
40
|
+
{left.map((r) => (
|
|
41
|
+
<AgentRequest key={r.id} agent={r.agent} kind={r.kind} since={r.since} title={r.title} detail={r.detail} onOpen={() => {}}
|
|
42
|
+
actions={<>{r.answers.map((a) => <Button key={a} size="sm" variant="secondary" onClick={() => done(r.id)}>{a}</Button>)}
|
|
43
|
+
{r.decline && <Button size="sm" variant="ghost" onClick={() => done(r.id)}>{r.decline}</Button>}</>} />
|
|
44
|
+
))}
|
|
45
|
+
</AgentRequests>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The full preview, as rendered in Storybook and checked in CI (`ar` is the preview page's own layout class, not Altitude's):
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import * as React from 'react';
|
|
52
|
+
import { AgentRequest, AgentRequests, Button } from 'altitude-design';
|
|
53
|
+
|
|
54
|
+
const now = Date.UTC(2026, 9, 31, 14, 20), min = 60000;
|
|
55
|
+
const seed = [
|
|
56
|
+
{ id: 'emails', agent: 'Renewal emails', kind: 'question', since: now - 4 * min, title: 'Send the offer to every monthly customer, or only the active ones?', answers: ['Active only', 'Everyone'] },
|
|
57
|
+
{ id: 'api', agent: 'Billing API', kind: 'approval', since: now - 9 * min, title: 'Run the plans-table migration on staging?', detail: 'Adds annual price columns to 4 plans on staging. Production isn\'t touched.', answers: ['Run migration'], decline: "Don't run" },
|
|
58
|
+
{ id: 'qa', agent: 'QA', kind: 'failed', since: now - 1 * min, title: 'Checkout tests failed', detail: '2 of 14 failed: the annual total shows $290/yr, expected $290/year.', answers: ['Retry'] },
|
|
59
|
+
];
|
|
60
|
+
export default function App() {
|
|
61
|
+
const [left, setLeft] = React.useState(seed);
|
|
62
|
+
const done = (id) => setLeft((l) => l.filter((r) => r.id !== id));
|
|
63
|
+
return (
|
|
64
|
+
<div className="ar">
|
|
65
|
+
<AgentRequests now={now}>
|
|
66
|
+
{left.map((r) => (
|
|
67
|
+
<AgentRequest key={r.id} agent={r.agent} kind={r.kind} since={r.since} title={r.title} detail={r.detail} onOpen={() => {}}
|
|
68
|
+
actions={<>{r.answers.map((a) => <Button key={a} size="sm" variant="secondary" onClick={() => done(r.id)}>{a}</Button>)}
|
|
69
|
+
{r.decline && <Button size="sm" variant="ghost" onClick={() => done(r.id)}>{r.decline}</Button>}</>} />
|
|
70
|
+
))}
|
|
71
|
+
</AgentRequests>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
<!-- /example -->
|