Skip to content

Commit 02c86d4

Browse files
committed
πŸ“ Restructure docs nav, add OCR doc, and humanize copy
Three-group nav (Introduction, User Guide, Architecture & API) with overview as the landing page. New Screen OCR doc. API methods grouped by domain. Architecture SVG diagram. Spacing tightened across all docs. Homepage copy rewritten. Humanizer pass on all docs and landing page.
1 parent d8a8bc3 commit 02c86d4

15 files changed

Lines changed: 944 additions & 756 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/** @type {import('@arach/arc').ArcDiagramData} */
2+
const diagram = {
3+
layout: { width: 720, height: 340 },
4+
nodes: {
5+
agents: { x: 255, y: 20, size: 'l' },
6+
app: { x: 255, y: 130, size: 'l' },
7+
cli: { x: 255, y: 240, size: 'l' },
8+
tmux: { x: 510, y: 240, size: 'm' },
9+
ocr: { x: 510, y: 130, size: 'm' },
10+
daemon: { x: 30, y: 130, size: 'm' },
11+
},
12+
nodeData: {
13+
agents: { icon: 'Bot', name: 'AI Agents / Scripts', subtitle: 'Daemon API consumers', color: 'violet' },
14+
app: { icon: 'Monitor', name: 'Menu Bar App', subtitle: 'Swift / AppKit', color: 'blue' },
15+
cli: { icon: 'Terminal', name: 'CLI', subtitle: 'Node.js', color: 'emerald' },
16+
tmux: { icon: 'Columns', name: 'tmux', subtitle: 'optional', color: 'zinc' },
17+
ocr: { icon: 'Eye', name: 'OCR Engine', subtitle: 'Vision + FTS5', color: 'amber' },
18+
daemon: { icon: 'Radio', name: 'Daemon', subtitle: 'WebSocket :9399', color: 'sky' },
19+
},
20+
connectors: [
21+
{ from: 'agents', to: 'daemon', fromAnchor: 'left', toAnchor: 'top', style: 'api' },
22+
{ from: 'app', to: 'daemon', fromAnchor: 'left', toAnchor: 'right', style: 'runs' },
23+
{ from: 'app', to: 'ocr', fromAnchor: 'right', toAnchor: 'left', style: 'reads' },
24+
{ from: 'app', to: 'cli', fromAnchor: 'bottom', toAnchor: 'top', style: 'calls' },
25+
{ from: 'cli', to: 'tmux', fromAnchor: 'right', toAnchor: 'left', style: 'optional' },
26+
],
27+
connectorStyles: {
28+
api: { color: 'violet', strokeWidth: 2, label: '30 RPC methods' },
29+
runs: { color: 'sky', strokeWidth: 2, label: 'runs' },
30+
reads: { color: 'amber', strokeWidth: 2, label: 'Vision scans' },
31+
calls: { color: 'emerald', strokeWidth: 2, label: 'calls' },
32+
optional: { color: 'zinc', strokeWidth: 2, label: 'optional', dashed: true },
33+
},
34+
}
35+
36+
export default diagram
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generate SVG from Arc diagram definition.
4+
* Usage: node diagrams/generate-svg.mjs
5+
*/
6+
import { readFileSync, writeFileSync } from 'fs'
7+
import { dirname, join } from 'path'
8+
import { fileURLToPath } from 'url'
9+
10+
const __dirname = dirname(fileURLToPath(import.meta.url))
11+
12+
// Inline the needed constants and functions from @arach/arc
13+
const NODE_SIZES = {
14+
xs: { width: 80, height: 36 },
15+
s: { width: 95, height: 42 },
16+
m: { width: 145, height: 68 },
17+
l: { width: 210, height: 85 },
18+
}
19+
20+
const nodeColors = {
21+
violet: { bg: '#8b5cf6', text: '#ffffff' },
22+
emerald: { bg: '#34d399', text: '#ffffff' },
23+
blue: { bg: '#60a5fa', text: '#ffffff' },
24+
amber: { bg: '#fbbf24', text: '#1f2937' },
25+
zinc: { bg: '#71717a', text: '#ffffff' },
26+
sky: { bg: '#38bdf8', text: '#ffffff' },
27+
rose: { bg: '#f43f5e', text: '#ffffff' },
28+
orange: { bg: '#fb923c', text: '#ffffff' },
29+
}
30+
31+
function escapeXml(str) {
32+
if (!str) return ''
33+
return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')
34+
}
35+
36+
function getAnchorPosition(x, y, width, height, position) {
37+
switch (position) {
38+
case 'left': return { x: x, y: y + height / 2 }
39+
case 'right': return { x: x + width, y: y + height / 2 }
40+
case 'top': return { x: x + width / 2, y: y }
41+
case 'bottom': return { x: x + width / 2, y: y + height }
42+
case 'bottomRight': return { x: x + width, y: y + height - 15 }
43+
case 'bottomLeft': return { x: x, y: y + height - 15 }
44+
case 'topRight': return { x: x + width, y: y + 15 }
45+
case 'topLeft': return { x: x, y: y + 15 }
46+
default: return { x: x + width / 2, y: y + height / 2 }
47+
}
48+
}
49+
50+
function getConnectorPath(from, to, fromAnchor, toAnchor) {
51+
const dx = to.x - from.x
52+
const dy = to.y - from.y
53+
let cp1x = from.x, cp1y = from.y, cp2x = to.x, cp2y = to.y
54+
const offset = Math.min(Math.abs(dx), Math.abs(dy), 50)
55+
56+
if (['right', 'bottomRight', 'topRight'].includes(fromAnchor)) cp1x = from.x + offset
57+
else if (['left', 'bottomLeft', 'topLeft'].includes(fromAnchor)) cp1x = from.x - offset
58+
else if (fromAnchor === 'bottom') cp1y = from.y + offset
59+
else if (fromAnchor === 'top') cp1y = from.y - offset
60+
61+
if (['left', 'bottomLeft', 'topLeft'].includes(toAnchor)) cp2x = to.x - offset
62+
else if (['right', 'bottomRight', 'topRight'].includes(toAnchor)) cp2x = to.x + offset
63+
else if (toAnchor === 'top') cp2y = to.y - offset
64+
else if (toAnchor === 'bottom') cp2y = to.y + offset
65+
66+
return `M ${from.x} ${from.y} C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${to.x} ${to.y}`
67+
}
68+
69+
function generateSVG(diagram, options = {}) {
70+
const { backgroundColor = 'transparent', padding = 20 } = options
71+
const bounds = { x: 0, y: 0, width: diagram.layout.width, height: diagram.layout.height }
72+
const viewBox = `${bounds.x - padding} ${bounds.y - padding} ${bounds.width + padding * 2} ${bounds.height + padding * 2}`
73+
const svgWidth = bounds.width + padding * 2
74+
const svgHeight = bounds.height + padding * 2
75+
76+
let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${svgHeight}" viewBox="${viewBox}">\n`
77+
78+
if (backgroundColor !== 'transparent') {
79+
svg += ` <rect x="${bounds.x - padding}" y="${bounds.y - padding}" width="${svgWidth}" height="${svgHeight}" fill="${backgroundColor}"/>\n`
80+
}
81+
82+
// Connectors
83+
for (const connector of diagram.connectors) {
84+
const fromNode = diagram.nodes[connector.from]
85+
const toNode = diagram.nodes[connector.to]
86+
if (!fromNode || !toNode) continue
87+
88+
const fromSize = NODE_SIZES[fromNode.size] || NODE_SIZES.m
89+
const toSize = NODE_SIZES[toNode.size] || NODE_SIZES.m
90+
const style = diagram.connectorStyles?.[connector.style] || { color: 'zinc', strokeWidth: 2 }
91+
const strokeColor = nodeColors[style.color]?.bg || '#71717a'
92+
93+
const fromPos = getAnchorPosition(fromNode.x, fromNode.y, fromSize.width, fromSize.height, connector.fromAnchor)
94+
const toPos = getAnchorPosition(toNode.x, toNode.y, toSize.width, toSize.height, connector.toAnchor)
95+
const path = getConnectorPath(fromPos, toPos, connector.fromAnchor, connector.toAnchor)
96+
97+
svg += ` <path d="${path}" fill="none" stroke="${strokeColor}" stroke-width="${style.strokeWidth || 2}"${style.dashed ? ' stroke-dasharray="6 3"' : ''} opacity="0.7"/>\n`
98+
svg += ` <circle cx="${toPos.x}" cy="${toPos.y}" r="3.5" fill="${strokeColor}" opacity="0.7"/>\n`
99+
100+
if (style.label) {
101+
const midX = (fromPos.x + toPos.x) / 2
102+
const midY = (fromPos.y + toPos.y) / 2
103+
svg += ` <text x="${midX}" y="${midY - 8}" text-anchor="middle" fill="${strokeColor}" font-size="10" font-family="ui-sans-serif, system-ui, sans-serif" opacity="0.8">${escapeXml(style.label)}</text>\n`
104+
}
105+
}
106+
107+
// Nodes
108+
for (const [nodeId, node] of Object.entries(diagram.nodes)) {
109+
const data = diagram.nodeData[nodeId]
110+
if (!data) continue
111+
112+
const size = NODE_SIZES[node.size] || NODE_SIZES.m
113+
const width = size.width
114+
const height = size.height
115+
const colors = nodeColors[data.color] || nodeColors.zinc
116+
117+
svg += ` <rect x="${node.x}" y="${node.y}" width="${width}" height="${height}" rx="12" fill="${colors.bg}"/>\n`
118+
119+
const fontSize = node.size === 'xs' ? 10 : node.size === 's' ? 11 : 13
120+
const nameY = data.subtitle ? node.y + height / 2 : node.y + height / 2 + fontSize / 3
121+
svg += ` <text x="${node.x + width / 2}" y="${nameY}" text-anchor="middle" fill="${colors.text}" font-size="${fontSize}" font-weight="600" font-family="ui-sans-serif, system-ui, sans-serif">${escapeXml(data.name)}</text>\n`
122+
123+
if (data.subtitle && height >= 60) {
124+
svg += ` <text x="${node.x + width / 2}" y="${nameY + fontSize + 4}" text-anchor="middle" fill="${colors.text}" opacity="0.75" font-size="${fontSize - 2}" font-family="ui-sans-serif, system-ui, sans-serif">${escapeXml(data.subtitle)}</text>\n`
125+
}
126+
}
127+
128+
svg += `</svg>`
129+
return svg
130+
}
131+
132+
// Load and generate
133+
const diagram = (await import('./architecture.diagram.js')).default
134+
const svg = generateSVG(diagram)
135+
const outPath = join(__dirname, '..', 'public', 'architecture.svg')
136+
writeFileSync(outPath, svg)
137+
console.log(`Generated ${outPath}`)

β€Ždocs-site/nav.jsonβ€Ž

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22
"name": "lattices",
33
"groups": [
44
{
5-
"id": "getting-started",
6-
"title": "Getting Started",
5+
"id": "introduction",
6+
"title": "Introduction",
77
"items": [
88
{
9-
"id": "concepts",
10-
"title": "Concepts",
11-
"description": "Core ideas, glossary, and architecture of lattices"
9+
"id": "overview",
10+
"title": "Overview",
11+
"description": "What lattices is and who it's for"
1212
},
13+
{
14+
"id": "quickstart",
15+
"title": "Quickstart",
16+
"description": "Install lattices and launch your first workspace in 2 minutes"
17+
}
18+
]
19+
},
20+
{
21+
"id": "user-guide",
22+
"title": "User Guide",
23+
"items": [
1324
{
1425
"id": "config",
1526
"title": "Configuration",
@@ -24,13 +35,23 @@
2435
"id": "layers",
2536
"title": "Layers & Tab Groups",
2637
"description": "Switchable workspace layers and tabbed project groups"
38+
},
39+
{
40+
"id": "ocr",
41+
"title": "Screen OCR",
42+
"description": "Vision-powered screen reading with full-text search"
2743
}
2844
]
2945
},
3046
{
31-
"id": "reference",
32-
"title": "Reference",
47+
"id": "architecture-api",
48+
"title": "Architecture & API",
3349
"items": [
50+
{
51+
"id": "concepts",
52+
"title": "Concepts",
53+
"description": "Core ideas, glossary, and architecture of lattices"
54+
},
3455
{
3556
"id": "api",
3657
"title": "Daemon API",
Lines changed: 35 additions & 0 deletions
Loading

β€Ždocs-site/src/layouts/DocsLayout.astroβ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ const { title = 'Docs', headings = [], description } = Astro.props
1515
</aside>
1616

1717
<article class="min-w-0">
18-
<div class="mb-10">
18+
<div class="mb-6">
1919
<h1 class="text-3xl md:text-4xl font-semibold tracking-tight">{title}</h1>
2020
{description && (
21-
<p class="mt-3 text-lg text-[var(--color-text-muted)]">{description}</p>
21+
<p class="mt-2 text-lg text-[var(--color-text-muted)]">{description}</p>
2222
)}
2323
</div>
2424
<div class="doc-content">

0 commit comments

Comments
Β (0)