@arach/arc 0.4.5 → 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/docs/llm.txt ADDED
@@ -0,0 +1,208 @@
1
+ # Arc - LLM Context File
2
+
3
+ > Diagrams as code — visual editor + React renderer for architecture diagrams
4
+ > Source: https://github.com/arach/arc
5
+ > Docs: https://arc.arach.dev/docs
6
+ > Contributor guide: CLAUDE.md (repo root — read before editor changes)
7
+
8
+ ## Quick Facts
9
+
10
+ - **What**: Drag-and-drop studio + `<ArcDiagram />` player for typed architecture diagrams
11
+ - **Output**: JSON / TypeScript configs (not images) — diffable, versionable
12
+ - **Stack**: React 19, Vite 7, TailwindCSS 4, Hudson shell (editor chrome)
13
+ - **Package manager**: bun (`bun install`, `bun run dev`)
14
+ - **MCP**: `arc-mcp` bin — see `docs/agent/mcp.agent.md`
15
+
16
+ ## Which Package?
17
+
18
+ | Goal | Package / path |
19
+ |------|----------------|
20
+ | Render a 2D diagram in React | `@arach/arc` or `@arach/arc-viewer` |
21
+ | Mermaid sequence diagrams | `@arach/arc-viewer` (`ArcMermaidPlayer`) |
22
+ | Isometric / YAML tier diagrams | `@arach/arc-iso` |
23
+ | Full visual studio | Clone repo → `bun run dev` → `/editor` |
24
+ | Contribute to editor | This repo — see CLAUDE.md |
25
+
26
+ `@arach/arc` is the main publish surface (player + editor components + utilities).
27
+ `@arach/arc-viewer` is a slimmer install when you only need read-only rendering.
28
+
29
+ ## Commands
30
+
31
+ ```bash
32
+ bun install
33
+ bun run dev # → http://localhost:5188/editor
34
+ bun run build
35
+ bun run lint
36
+ bun run typecheck
37
+ ```
38
+
39
+ ## Canonical Diagram Schema
40
+
41
+ Source of truth: `src/types/diagram.ts`
42
+
43
+ ```typescript
44
+ interface ArcDiagramData {
45
+ id?: string
46
+ layout: { width: number; height: number }
47
+ layoutHints?: LayoutHints // auto-layout inside groups
48
+ nodes: Record<string, { x: number; y: number; size: 'xs' | 's' | 'm' | 'l' }>
49
+ nodeData: Record<string, {
50
+ icon: string // Lucide name as string
51
+ name: string
52
+ subtitle?: string
53
+ description?: string
54
+ color: DiagramColor
55
+ shape?: NodeShape // per-node silhouette override
56
+ }>
57
+ connectors: Array<{
58
+ from: string; to: string
59
+ fromAnchor: AnchorPosition; toAnchor: AnchorPosition
60
+ style: string
61
+ curve?: 'natural' | 'step'
62
+ }>
63
+ connectorStyles: Record<string, {
64
+ color: DiagramColor
65
+ strokeWidth: number
66
+ label?: string
67
+ dashed?: boolean
68
+ }>
69
+ groups?: GroupShape[]
70
+ focusTargets?: Record<string, FocusTarget>
71
+ }
72
+ ```
73
+
74
+ **Node sizes** (`src/utils/constants.ts` NODE_SIZES):
75
+ - `xs` 80×36 · `s` 110×48 · `m` 160×75 · `l` 220×90
76
+
77
+ **Do not use** legacy Talkie sizes (`large`, `normal`, `small`) — see HANDOFF.md.
78
+
79
+ **`_meta`** (saved with files, round-trips through sessions):
80
+ `themeId`, `colorMode`, `viewMode`, `isoStyle`, `viewport`
81
+
82
+ Validate external JSON with `validateDiagramShape()` in `src/utils/diagramValidation.ts`.
83
+
84
+ ## Minimal Example
85
+
86
+ ```tsx
87
+ import { ArcDiagram } from '@arach/arc'
88
+ import type { ArcDiagramData } from '@arach/arc'
89
+
90
+ const diagram: ArcDiagramData = {
91
+ layout: { width: 600, height: 300 },
92
+ nodes: {
93
+ frontend: { x: 50, y: 100, size: 'm' },
94
+ backend: { x: 250, y: 100, size: 'm' },
95
+ },
96
+ nodeData: {
97
+ frontend: { icon: 'Monitor', name: 'Frontend', color: 'violet' },
98
+ backend: { icon: 'Server', name: 'Backend', color: 'emerald' },
99
+ },
100
+ connectors: [
101
+ { from: 'frontend', to: 'backend', fromAnchor: 'right', toAnchor: 'left', style: 'api' },
102
+ ],
103
+ connectorStyles: {
104
+ api: { color: 'violet', strokeWidth: 2, label: 'REST' },
105
+ },
106
+ }
107
+
108
+ <ArcDiagram data={diagram} mode="light" theme="default" defaultZoom="fit" />
109
+ ```
110
+
111
+ ## Auto-layout (prefer over hand-positioning)
112
+
113
+ ```typescript
114
+ import { autoLayout } from '@arach/arc'
115
+
116
+ const laidOut = autoLayout({
117
+ nodeData: { /* ... */ },
118
+ connectors: [/* omit anchors — inferred */],
119
+ connectorStyles: { /* ... */ },
120
+ })
121
+ ```
122
+
123
+ See `docs/group-layout.md` for `layoutHints` / group frames.
124
+
125
+ ## Project Structure (contributors)
126
+
127
+ ```
128
+ src/
129
+ ├── main.tsx / App.tsx # Routes: /, /editor, /player, /showcase, /docs
130
+ ├── apps/
131
+ │ ├── arc-editor/ # Hudson shell wrapper (canonical editor UX)
132
+ │ └── arc-showcase/ # Player harness with inspector controls
133
+ ├── components/
134
+ │ ├── ArcDiagram.tsx # 2D player (also exported as @arach/arc)
135
+ │ ├── editor/ # Canvas, reducer, nodes, connectors
136
+ │ │ ├── editorReducer.ts # All state transitions
137
+ │ │ ├── DiagramCanvas.tsx
138
+ │ │ ├── DiagramEditor.tsx # Legacy layout (canvas still lives here)
139
+ │ │ └── EditorProvider.tsx
140
+ │ ├── chrome/ # Settings rail, markup pane (CodeMirror)
141
+ │ └── diagrams/*.diagram.ts # Canonical examples — copy these
142
+ ├── types/diagram.ts # Public diagram schema
143
+ ├── utils/ # themes, validation, export, autoLayout, icons
144
+ └── iso/ # Isometric renderer
145
+
146
+ packages/
147
+ ├── viewer/ # @arach/arc-viewer (+ Mermaid)
148
+ └── iso/ # @arach/arc-iso
149
+ ```
150
+
151
+ **Editor entry flow**: `/editor` → `createArcEditorApp()` (Hudson) → `ArcEditorContent` → `DiagramEditor` / `DiagramCanvas`.
152
+ Shell chrome (nav, rail, inspector, markup pane) is Hudson + `src/components/chrome/`.
153
+ Canvas logic and reducer are in `src/components/editor/`.
154
+
155
+ ## State Shape
156
+
157
+ ```typescript
158
+ {
159
+ diagram: { layout, grid, nodes, nodeData, connectors, connectorStyles, groups, images, exportZone },
160
+ editor: { selectedNodeIds, selectedConnectorIndex, mode, pendingConnector, viewMode, isoStyle, themeId, colorMode, ... },
161
+ meta: { filename, isDirty, lastSaved, diagramMeta },
162
+ history: { past, future } // capped at 50
163
+ }
164
+ ```
165
+
166
+ Reducer actions: see `docs/agent/editor-actions.agent.md`
167
+
168
+ ## Themes
169
+
170
+ Diagram themes (8): `default`, `warm`, `cool`, `mono`, `engineering`, `workbench`, `tactical`, `command`
171
+ Chrome skins (shell only): `console`, `graphite`, `amber`, `viridian`, `paper`
172
+
173
+ ## Visual Verification
174
+
175
+ ```bash
176
+ bun run dev
177
+ # Open http://localhost:5188/editor/:sessionId
178
+ # Or http://localhost:5188/showcase?doc=platform
179
+
180
+ # Dev-only PNG capture:
181
+ curl "http://localhost:5188/capture/my-session" > out.png
182
+ ```
183
+
184
+ ## Agent Artifacts
185
+
186
+ | File | Purpose |
187
+ |------|---------|
188
+ | CLAUDE.md | Full contributor context (best doc in repo) |
189
+ | docs/llm.txt | This file — dense agent briefing |
190
+ | docs/agent/*.agent.md | Per-topic agent context |
191
+ | skills/arc-diagrams/SKILL.md | Diagram generation skill |
192
+ | docs/prompts/*.md | Task prompt templates |
193
+ | public/llms.txt | Served at /llms.txt (summary) |
194
+
195
+ ## MCP
196
+
197
+ `arc-mcp` bin on `@arach/arc` (`bun run mcp` in dev, `bun run build:mcp` for publish).
198
+ Tools: validate_diagram, auto_layout, render_ascii, diagram_to_typescript, editor_handoff.
199
+ See docs/agent/mcp.agent.md for coverage gaps (SVG/PNG/Mermaid stay elsewhere).
200
+
201
+ ## Critical Rules
202
+
203
+ 1. Diagrams are JSON data — keep them declarative and deterministic
204
+ 2. Node sizes are `xs` | `s` | `m` | `l` only
205
+ 3. Icons are Lucide **string names**, registered in `src/utils/iconRegistry.ts`
206
+ 4. State is immutable — reducer returns new objects; history on diagram mutations
207
+ 5. `diagram/replace` (markup pane) pushes history; `diagram/load` does not
208
+ 6. Read CLAUDE.md before touching chrome tokens, markup pane, or canvas overlays
@@ -0,0 +1,182 @@
1
+ import { jsxs as j, jsx as a } from "react/jsx-runtime";
2
+ import { useState as f, useRef as N, useMemo as Z, useEffect as C, useCallback as h } from "react";
3
+ import { C as q, a as B, X as G, b as Q, v as V } from "./index-CbEv7M-m.js";
4
+ const L = "arc-markup-width", u = 460, D = 280, ee = 0.68, P = 24;
5
+ function T() {
6
+ return typeof window > "u" ? u : Math.max(D, Math.round(window.innerWidth * ee));
7
+ }
8
+ const M = (r) => Math.min(Math.max(r, D), T());
9
+ function te() {
10
+ if (typeof window > "u") return u;
11
+ try {
12
+ const r = Number(window.localStorage.getItem(L));
13
+ return Number.isFinite(r) && r >= D ? M(r) : u;
14
+ } catch {
15
+ return u;
16
+ }
17
+ }
18
+ function I(r) {
19
+ try {
20
+ window.localStorage.setItem(L, String(r));
21
+ } catch {
22
+ }
23
+ }
24
+ function re(r, o = "diagram") {
25
+ const w = JSON.stringify(r, null, 2).replace(/"([^"]+)":/g, "$1:").replace(/"/g, "'");
26
+ return `import type { ArcDiagramData } from '@arach/arc'
27
+
28
+ const ${o}: ArcDiagramData = ${w}
29
+
30
+ export default ${o}
31
+ `;
32
+ }
33
+ function oe({ title: r, data: o, onApply: w, onClose: S }) {
34
+ const [n, A] = f("json"), [W, $] = f(!1), [g, s] = f({ kind: "clean" }), [J, k] = f(null), i = N(null), d = N(null), m = N(null), [c, y] = f(te), l = N(null), b = Z(
35
+ () => n === "ts" ? re(o) : JSON.stringify(o, null, 2),
36
+ [n, o]
37
+ ), E = n === "json" && !!w, R = J ?? b;
38
+ C(() => {
39
+ if (m.current !== null && m.current === b) {
40
+ m.current = null;
41
+ return;
42
+ }
43
+ m.current = null, k(null), s({ kind: "clean" });
44
+ }, [b]), C(() => {
45
+ const e = (t) => {
46
+ t.key !== "Escape" || t.target?.closest?.(".cm-editor") || S();
47
+ };
48
+ return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
49
+ }, [S]), C(() => () => {
50
+ i.current && window.clearTimeout(i.current), d.current && window.clearTimeout(d.current);
51
+ }, []), C(() => {
52
+ const e = () => y((t) => t > T() ? T() : t);
53
+ return window.addEventListener("resize", e), () => window.removeEventListener("resize", e);
54
+ }, []);
55
+ const X = h((e) => {
56
+ e.preventDefault(), e.target.setPointerCapture(e.pointerId), l.current = { startX: e.clientX, startWidth: c };
57
+ }, [c]), F = h((e) => {
58
+ l.current && y(M(l.current.startWidth + (e.clientX - l.current.startX)));
59
+ }, []), x = h((e) => {
60
+ l.current && (l.current = null, e.target.releasePointerCapture?.(e.pointerId), I(c));
61
+ }, [c]), _ = h(() => {
62
+ y(u), I(u);
63
+ }, []), K = h((e) => {
64
+ const t = e.key === "ArrowLeft" ? -P : e.key === "ArrowRight" ? P : 0;
65
+ t && (e.preventDefault(), y((p) => {
66
+ const v = M(p + t);
67
+ return I(v), v;
68
+ }));
69
+ }, []), H = (e) => {
70
+ if (e === b) {
71
+ i.current && window.clearTimeout(i.current), k(null);
72
+ return;
73
+ }
74
+ k(e), E && (i.current && window.clearTimeout(i.current), i.current = window.setTimeout(() => {
75
+ let t;
76
+ try {
77
+ t = JSON.parse(e);
78
+ } catch (v) {
79
+ s({ kind: "error", message: v.message.replace(/^JSON\.parse: /, "") });
80
+ return;
81
+ }
82
+ const p = V(t);
83
+ if (p) {
84
+ s({ kind: "error", message: p });
85
+ return;
86
+ }
87
+ m.current = JSON.stringify(t, null, 2), w?.(t), s({ kind: "applied" });
88
+ }, 400));
89
+ }, z = (e) => {
90
+ e !== n && (i.current && window.clearTimeout(i.current), k(null), s({ kind: "clean" }), A(e));
91
+ }, U = async () => {
92
+ try {
93
+ await navigator.clipboard.writeText(R), $(!0), d.current && window.clearTimeout(d.current), d.current = window.setTimeout(() => $(!1), 1600);
94
+ } catch {
95
+ s({ kind: "error", message: "Clipboard unavailable — select the text and copy" });
96
+ }
97
+ }, O = R.split(`
98
+ `).length, Y = g.kind === "error" ? `⚠ ${g.message}` : g.kind === "applied" ? `${O} lines · applied to canvas` : `${O} lines · ${n === "ts" ? "read-only export" : E ? "edit to update the diagram" : "diagram JSON"}`;
99
+ return /* @__PURE__ */ j("aside", { className: "arc-markup-pane", style: { width: c }, "aria-label": "Diagram markup", children: [
100
+ /* @__PURE__ */ j("div", { className: "arc-markup-head", children: [
101
+ /* @__PURE__ */ a("span", { className: "arc-markup-title", title: r, children: r }),
102
+ /* @__PURE__ */ j("div", { className: "arc-markup-formats", role: "group", "aria-label": "Markup format", children: [
103
+ /* @__PURE__ */ a(
104
+ "button",
105
+ {
106
+ type: "button",
107
+ className: `arc-settings-segment${n === "json" ? " is-active" : ""}`,
108
+ "aria-pressed": n === "json",
109
+ onClick: () => z("json"),
110
+ children: ".json"
111
+ }
112
+ ),
113
+ /* @__PURE__ */ a(
114
+ "button",
115
+ {
116
+ type: "button",
117
+ className: `arc-settings-segment${n === "ts" ? " is-active" : ""}`,
118
+ "aria-pressed": n === "ts",
119
+ onClick: () => z("ts"),
120
+ children: ".ts"
121
+ }
122
+ )
123
+ ] }),
124
+ /* @__PURE__ */ a(
125
+ "button",
126
+ {
127
+ type: "button",
128
+ className: "arc-editor-btn",
129
+ onClick: U,
130
+ title: W ? "Copied" : "Copy markup",
131
+ "aria-label": "Copy markup",
132
+ children: W ? /* @__PURE__ */ a(q, {}) : /* @__PURE__ */ a(B, {})
133
+ }
134
+ ),
135
+ /* @__PURE__ */ a("button", { type: "button", className: "arc-editor-btn", onClick: S, title: "Close", "aria-label": "Close markup", children: /* @__PURE__ */ a(G, {}) })
136
+ ] }),
137
+ /* @__PURE__ */ a("div", { className: "arc-markup-source", children: /* @__PURE__ */ a(
138
+ Q,
139
+ {
140
+ code: R,
141
+ language: n === "ts" ? "typescript" : "json",
142
+ filename: n === "ts" ? "diagram.ts" : "diagram.json",
143
+ readOnly: !E,
144
+ showLineNumbers: !0,
145
+ onChange: H
146
+ },
147
+ n
148
+ ) }),
149
+ /* @__PURE__ */ a(
150
+ "div",
151
+ {
152
+ className: `arc-markup-foot${g.kind === "error" ? " is-error" : ""}`,
153
+ role: "status",
154
+ "aria-live": "polite",
155
+ children: Y
156
+ }
157
+ ),
158
+ /* @__PURE__ */ a(
159
+ "div",
160
+ {
161
+ className: "arc-markup-resizer",
162
+ role: "separator",
163
+ tabIndex: 0,
164
+ "aria-orientation": "vertical",
165
+ "aria-label": "Resize markup pane",
166
+ "aria-valuenow": c,
167
+ "aria-valuemin": D,
168
+ "aria-valuemax": T(),
169
+ onPointerDown: X,
170
+ onPointerMove: F,
171
+ onPointerUp: x,
172
+ onPointerCancel: x,
173
+ onDoubleClick: _,
174
+ onKeyDown: K
175
+ }
176
+ )
177
+ ] });
178
+ }
179
+ export {
180
+ oe as default,
181
+ re as toTsSource
182
+ };