sitelines 1.0.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/DESIGN.md +129 -0
- package/LICENSE +21 -0
- package/README.md +336 -0
- package/bin/sitelines.mjs +155 -0
- package/examples/demo-site/about/index.html +30 -0
- package/examples/demo-site/assets/auth.js +3 -0
- package/examples/demo-site/assets/site.css +27 -0
- package/examples/demo-site/blog/index.html +31 -0
- package/examples/demo-site/blog/introducing-meridian/index.html +30 -0
- package/examples/demo-site/changelog/index.html +30 -0
- package/examples/demo-site/contact/index.html +33 -0
- package/examples/demo-site/docs/api/index.html +31 -0
- package/examples/demo-site/docs/api/webhooks/index.html +30 -0
- package/examples/demo-site/docs/api/webhooks/retries/index.html +30 -0
- package/examples/demo-site/docs/index.html +32 -0
- package/examples/demo-site/docs/quickstart/index.html +31 -0
- package/examples/demo-site/index.html +37 -0
- package/examples/demo-site/legal/privacy/index.html +29 -0
- package/examples/demo-site/legal/terms/index.html +29 -0
- package/examples/demo-site/login/index.html +35 -0
- package/examples/demo-site/pricing/index.html +33 -0
- package/examples/demo-site/product/index.html +31 -0
- package/examples/demo-site/product/integrations/index.html +31 -0
- package/examples/demo-site/signup/index.html +35 -0
- package/examples/demo-site/status/index.html +15 -0
- package/examples/demo-site/thanks/index.html +21 -0
- package/package.json +53 -0
- package/scripts/install-skill.mjs +145 -0
- package/scripts/scan.mjs +399 -0
- package/scripts/serve.mjs +207 -0
- package/skill/AGENTS.md +61 -0
- package/skill/SKILL.md +170 -0
- package/skill/references/edit-protocol.md +57 -0
- package/viewer/app.js +1373 -0
- package/viewer/index.html +202 -0
- package/viewer/style.css +616 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# sitelines viewer - design
|
|
2
|
+
|
|
3
|
+
The authority for `viewer/style.css`. Read this before changing anything visual.
|
|
4
|
+
|
|
5
|
+
**Mode: Operate.** The visitor is a developer holding a question: *where does this button go, and what did I break?*
|
|
6
|
+
Scanability, state legibility, and keyboard reach outrank expression. The brand lives in precision, not in
|
|
7
|
+
personality. If a decision makes the map prettier and the answer slower, the decision is wrong.
|
|
8
|
+
|
|
9
|
+
## World
|
|
10
|
+
|
|
11
|
+
A **plan view**. The map is a technical drawing of a site: pages are cards laid out on a measured ground,
|
|
12
|
+
depth columns are ruled like a drafting grid, links are drawn wires with a legible arrowhead. Nothing is
|
|
13
|
+
themed, gamified, or narrated. The visual interest comes from real density and real hierarchy: forty live
|
|
14
|
+
page previews arranged by how many clicks they sit from the front door.
|
|
15
|
+
|
|
16
|
+
The tool runs beside an editor, all day, in whatever ambient light the developer already chose. So it ships
|
|
17
|
+
**both themes**, follows `prefers-color-scheme` by default, and offers a manual override that persists.
|
|
18
|
+
Neither theme is the "real" one.
|
|
19
|
+
|
|
20
|
+
## Tokens
|
|
21
|
+
|
|
22
|
+
Every value below is a CSS custom property in `:root`, re-declared under `[data-theme="dark"]` and
|
|
23
|
+
`@media (prefers-color-scheme: dark)`. Nothing in the stylesheet may hardcode a color.
|
|
24
|
+
|
|
25
|
+
| role | light | dark |
|
|
26
|
+
| --- | --- | --- |
|
|
27
|
+
| `--bg` canvas | `#f5f6f8` | `#0d0f12` |
|
|
28
|
+
| `--surface` cards, panels, bar | `#ffffff` | `#16191e` |
|
|
29
|
+
| `--surface-2` insets, fields | `#eef0f4` | `#1d2127` |
|
|
30
|
+
| `--surface-3` pressed, hovered inset | `#e7eaef` | `#252a32` |
|
|
31
|
+
| `--text` primary | `#10131a` | `#eceef2` |
|
|
32
|
+
| `--text-2` secondary | `#3f4653` | `#b9bfc9` |
|
|
33
|
+
| `--text-3` tertiary | `#5c6472` | `#949ba6` |
|
|
34
|
+
| `--text-4` quietest legible | `#6f7784` | `#7f8794` |
|
|
35
|
+
| `--line` hairline | `#e2e5ea` | `#262b33` |
|
|
36
|
+
| `--line-2` defined edge | `#cdd2da` | `#363c46` |
|
|
37
|
+
| `--wire` link stroke | `#99a1af` | `#4e5663` |
|
|
38
|
+
|
|
39
|
+
Every text token clears 4.5:1 against the surface it is used on, in both themes. `--text-4` is the floor,
|
|
40
|
+
not a decorative gray: there is no token below AA.
|
|
41
|
+
|
|
42
|
+
**One accent, locked:** `--accent` `#2563eb` / `#5b9cff`. It means *selected, primary, interactive*. It is
|
|
43
|
+
the only non-status hue on the surface.
|
|
44
|
+
|
|
45
|
+
**Three status hues, each a meaning:**
|
|
46
|
+
|
|
47
|
+
| token | meaning | light | dark |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| `--danger` | a link that goes nowhere | `#d92d20` | `#ff6b5e` |
|
|
50
|
+
| `--warn` | queued, proposed, not yet in the code | `#b25e09` | `#f0b132` |
|
|
51
|
+
| `--ok` | an entry point, a way in | `#067a5b` | `#35c99a` |
|
|
52
|
+
|
|
53
|
+
Adding a color means adding a row to the legend. There is no decorative color.
|
|
54
|
+
|
|
55
|
+
## Link kind is drawn, not colored
|
|
56
|
+
|
|
57
|
+
A wire's **hue carries state** (normal, dead, queued, traced). A wire's **style carries kind**, so the four
|
|
58
|
+
navigation mechanisms stay distinguishable without spending four more hues:
|
|
59
|
+
|
|
60
|
+
| kind | stroke |
|
|
61
|
+
| --- | --- |
|
|
62
|
+
| anchor / button | solid |
|
|
63
|
+
| script navigation | dotted `1 4` |
|
|
64
|
+
| redirect | dashed `8 5` |
|
|
65
|
+
| form submit | solid, with a bar terminal at the source |
|
|
66
|
+
| queued (proposed) | dashed `7 5`, `--warn`, heavier |
|
|
67
|
+
|
|
68
|
+
## Shape
|
|
69
|
+
|
|
70
|
+
One radius system, no exceptions:
|
|
71
|
+
|
|
72
|
+
- `--r-1` **6px** - every interactive control: buttons, tabs, inputs, chips, badges, list rows.
|
|
73
|
+
- `--r-2` **10px** - every surface: page cards, panels, popovers, the modal, the preview frame.
|
|
74
|
+
- `--r-3` **14px** - containers that hold surfaces: the section backdrop.
|
|
75
|
+
|
|
76
|
+
A pill, a circle, or a square-cornered control is a bug. The one intentional circle is the I/O port dot,
|
|
77
|
+
which is a dot because it is a point of connection.
|
|
78
|
+
|
|
79
|
+
## Type
|
|
80
|
+
|
|
81
|
+
- `--ui`: the platform's own UI stack. Chrome, headings, labels, prose. No webfont: the viewer is served
|
|
82
|
+
from `node:http` with zero dependencies and must paint correctly offline, on the first frame.
|
|
83
|
+
- `--mono`: the platform's own mono stack. Reserved for **data**: routes, `file:line`, counts, link labels,
|
|
84
|
+
measurements. Mono here is not a costume for "technical" - every string set in it is an identifier or a
|
|
85
|
+
number the developer will compare against something else.
|
|
86
|
+
|
|
87
|
+
Hierarchy comes from weight and color, not scale. The size range across the whole UI is 11px to 19px.
|
|
88
|
+
Uppercase tracking appears in exactly two places: rail panel titles, and depth-column labels on the canvas.
|
|
89
|
+
Both are axis labels for a graph, not section eyebrows.
|
|
90
|
+
|
|
91
|
+
## Motion
|
|
92
|
+
|
|
93
|
+
`prefers-reduced-motion` collapses all of it. The budget is deliberately small.
|
|
94
|
+
|
|
95
|
+
- **One authored moment:** on first paint the map settles in, cards rising 6px and fading, staggered by
|
|
96
|
+
depth column so the site's shape reads left to right. It plays once per render, never on scroll.
|
|
97
|
+
- Everything else is feedback: 120ms on hover and focus, 90ms on `:active`, a wire trace on hover.
|
|
98
|
+
- Nothing loops. Nothing parallaxes. A skeleton shimmer marks a preview that is genuinely still loading.
|
|
99
|
+
|
|
100
|
+
## Density and honesty
|
|
101
|
+
|
|
102
|
+
The viewer is a cockpit, and it earns that density by never lying about it:
|
|
103
|
+
|
|
104
|
+
- Below `0.26` scale, wire labels and page thumbnails leave the render tree. A map that cannot show detail
|
|
105
|
+
says so by not drawing it, rather than compositing an unreadable smear.
|
|
106
|
+
- At most 12 preview iframes stay mounted, two loading at a time, page JS off by default. Offscreen cards
|
|
107
|
+
unmount.
|
|
108
|
+
- Past 60 pages, previews start off and the viewer says why.
|
|
109
|
+
|
|
110
|
+
## Icons
|
|
111
|
+
|
|
112
|
+
12px geometric glyphs from a single inline `<symbol>` sprite in `index.html`, one stroke weight (1.6),
|
|
113
|
+
`currentColor` throughout. An icon library is the right default and is deliberately not used here: the
|
|
114
|
+
project ships zero dependencies and no build step, so a CDN or an npm icon set would break both. That
|
|
115
|
+
constraint buys the sprite; it does not buy hand-drawn illustration. Every glyph is a rectangle, a line, a
|
|
116
|
+
circle, or a chevron.
|
|
117
|
+
|
|
118
|
+
No emoji anywhere in the UI.
|
|
119
|
+
|
|
120
|
+
## Anti-reference
|
|
121
|
+
|
|
122
|
+
Two worlds this replaced, both evidence of what the tool does rather than authority over how it looks:
|
|
123
|
+
|
|
124
|
+
1. A dark stone-and-torch "dungeon" theme with rooms, corridors, and quests.
|
|
125
|
+
2. A periwinkle "starchart" theme with sky gradients, star specks, and violet hero cards.
|
|
126
|
+
|
|
127
|
+
Do not reintroduce a spatial metaphor, a decorative ground texture, pill controls, all-mono chrome, or a
|
|
128
|
+
saturated gradient card. The nouns in this tool are **page**, **link**, **section**, **entry**, **exit**,
|
|
129
|
+
**issue**, and **change**. They are the product's language, and they are the only language the UI speaks.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sitelines contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# sitelines
|
|
4
|
+
|
|
5
|
+
**See every page on your site, every link between them, and everything that is broken.**
|
|
6
|
+
|
|
7
|
+
Zero dependencies · no build step · Node 18+ · MIT
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
sitelines reads your source, finds every page and every link, button, redirect, and form that moves between
|
|
14
|
+
them, then serves a map in your browser. Each card is a real page with a live preview. Each wire is a real
|
|
15
|
+
control, labelled with its actual button text and traced back to `file:line`.
|
|
16
|
+
|
|
17
|
+
It tells you what is wrong: links that go nowhere, pages nothing links to, pages with no way out, pages
|
|
18
|
+
buried more than three clicks deep, and pages carrying too many exits.
|
|
19
|
+
|
|
20
|
+
You can also change the navigation from the map. Rename a button, point it somewhere else, draw a new link
|
|
21
|
+
between two pages, propose a new page. **Nothing is written to your site.** Changes queue to a JSON file,
|
|
22
|
+
and a coding agent implements them in your code when you ask.
|
|
23
|
+
|
|
24
|
+
> The screenshots on this page are the bundled example site, mapped by sitelines itself. Run
|
|
25
|
+
> `npx sitelines demo` and you get exactly this.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## How to use it
|
|
30
|
+
|
|
31
|
+
**1. Point it at your pages.**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd your-project
|
|
35
|
+
npx sitelines open
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
That scans, starts a local server, and opens `http://localhost:4370`. It looks for your pages in `public`,
|
|
39
|
+
`site`, `www`, `static`, `src/pages`, `src/routes`, `src/app`, `app/pages`, `pages`, `app`, or `docs`.
|
|
40
|
+
Point it somewhere else with `--root`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx sitelines open --root src/routes --port 5000
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**2. Read the map.** Columns are click depth from your front door. Cards are pages. Wires are controls. The
|
|
47
|
+
left sidebar counts what exists and lists what is broken, and every issue jumps you to the page it is on.
|
|
48
|
+
|
|
49
|
+
**3. Change the navigation.** Edit an exit's text, change where it points, draw a new link, propose a page.
|
|
50
|
+
Everything you do queues up instead of touching your files.
|
|
51
|
+
|
|
52
|
+
**4. Hand the queue to an agent.** Press **Copy prompt**, or install the skill and say
|
|
53
|
+
*"apply my sitelines changes"*.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
Three ways in, depending on what you want.
|
|
60
|
+
|
|
61
|
+
### Option 1: the tool
|
|
62
|
+
|
|
63
|
+
Everything above. The scanner, the server, and the map.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx sitelines open # npm
|
|
67
|
+
pnpm dlx sitelines open # pnpm
|
|
68
|
+
bunx sitelines open # bun
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Or keep it around:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm install -g sitelines
|
|
75
|
+
pnpm add -g sitelines
|
|
76
|
+
bun add -g sitelines
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Every `npx` in this README works the same as `pnpm dlx` and `bunx`.** sitelines has no dependencies and
|
|
80
|
+
no build step, so there is nothing for a package manager to disagree about. It runs on Node 18+ and on Bun.
|
|
81
|
+
|
|
82
|
+
Or from source, with nothing to build:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/Jkaos725/sitelines.git
|
|
86
|
+
cd your-project
|
|
87
|
+
node /path/to/sitelines/bin/sitelines.mjs open
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Option 2: the demo
|
|
91
|
+
|
|
92
|
+
See what it does before pointing it at anything of yours. This copies a 20-page example site into
|
|
93
|
+
`./sitelines-demo` and maps it. Nothing outside that folder is touched.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npx sitelines demo
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The example has deliberate faults so every part of the map has something to show: two dead links (one of
|
|
100
|
+
them only reachable by reading the JavaScript), three orphans, a dead end, and a page four clicks deep.
|
|
101
|
+
|
|
102
|
+
### Option 3: the Claude Code skill
|
|
103
|
+
|
|
104
|
+
The same tool, plus the instructions an agent needs to turn your queued changes into real code. This is the
|
|
105
|
+
only option that can actually **write** the changes for you.
|
|
106
|
+
|
|
107
|
+
**Claude Code** reads skill folders:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npx sitelines skill install # ~/.claude/skills/sitelines (every project)
|
|
111
|
+
npx sitelines skill install --project # ./.claude/skills/sitelines (this repo only)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Codex, opencode, Cursor, Zed, Gemini CLI** read `AGENTS.md` from your project root:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx sitelines agents install # writes the block into ./AGENTS.md
|
|
118
|
+
npx sitelines agents uninstall # takes it back out
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
That appends a fenced block rather than overwriting, so an existing `AGENTS.md` keeps everything it
|
|
122
|
+
already had. Re-running replaces the block in place instead of duplicating it, and uninstalling leaves
|
|
123
|
+
your own content untouched.
|
|
124
|
+
|
|
125
|
+
<details>
|
|
126
|
+
<summary>Install it by hand instead</summary>
|
|
127
|
+
|
|
128
|
+
A Claude Code skill is just a folder containing a `SKILL.md`. Copy four things into it:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
git clone https://github.com/Jkaos725/sitelines.git
|
|
132
|
+
mkdir -p ~/.claude/skills/sitelines
|
|
133
|
+
|
|
134
|
+
cp sitelines/skill/SKILL.md ~/.claude/skills/sitelines/
|
|
135
|
+
cp -r sitelines/skill/references ~/.claude/skills/sitelines/
|
|
136
|
+
cp -r sitelines/scripts ~/.claude/skills/sitelines/
|
|
137
|
+
cp -r sitelines/viewer ~/.claude/skills/sitelines/
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
On Windows PowerShell:
|
|
141
|
+
|
|
142
|
+
```powershell
|
|
143
|
+
git clone https://github.com/Jkaos725/sitelines.git
|
|
144
|
+
New-Item -ItemType Directory -Force "$HOME\.claude\skills\sitelines"
|
|
145
|
+
|
|
146
|
+
Copy-Item sitelines\skill\SKILL.md "$HOME\.claude\skills\sitelines\"
|
|
147
|
+
Copy-Item sitelines\skill\references "$HOME\.claude\skills\sitelines\" -Recurse
|
|
148
|
+
Copy-Item sitelines\scripts "$HOME\.claude\skills\sitelines\" -Recurse
|
|
149
|
+
Copy-Item sitelines\viewer "$HOME\.claude\skills\sitelines\" -Recurse
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
</details>
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary>Or ask Claude Code to install it</summary>
|
|
156
|
+
|
|
157
|
+
Paste this in:
|
|
158
|
+
|
|
159
|
+
> Install the sitelines skill from https://github.com/Jkaos725/sitelines into `~/.claude/skills/sitelines`.
|
|
160
|
+
> Clone the repo to a temp directory, copy `skill/SKILL.md`, `skill/references/`, `scripts/`, and `viewer/`
|
|
161
|
+
> into the skill folder, then confirm `~/.claude/skills/sitelines/SKILL.md` exists.
|
|
162
|
+
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
Restart Claude Code, then type `/sitelines` or just ask:
|
|
166
|
+
|
|
167
|
+
- "show me the site flow"
|
|
168
|
+
- "where does the Start free button go?"
|
|
169
|
+
- "find the dead links"
|
|
170
|
+
- "apply my sitelines changes"
|
|
171
|
+
|
|
172
|
+
### All commands
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
sitelines scan [--root DIR] [--out DIR] read the site, write .sitelines/flow.json
|
|
176
|
+
sitelines serve [--root DIR] [--out DIR] [--port N] [--open] open the map
|
|
177
|
+
sitelines open [--root DIR] [--port N] scan, serve, and open the browser
|
|
178
|
+
sitelines demo [--dir DIR] [--port N] copy the example site and map it
|
|
179
|
+
sitelines skill install [--global|--project] [--force] install the Claude Code skill
|
|
180
|
+
sitelines skill uninstall [--global|--project] remove it
|
|
181
|
+
sitelines agents install [--dir DIR] write the AGENTS.md block (every other agent)
|
|
182
|
+
sitelines agents uninstall [--dir DIR] remove just that block
|
|
183
|
+
sitelines version
|
|
184
|
+
sitelines help
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Features
|
|
190
|
+
|
|
191
|
+
### It hides your header and footer, and says so
|
|
192
|
+
|
|
193
|
+
A nav bar repeated on twenty pages emits twenty identical links, and those wires bury every link that is
|
|
194
|
+
actually specific to a page. sitelines detects any link appearing on more than four pages, folds it away,
|
|
195
|
+
and reports the count it folded: **27 of 174** in the screenshot above. Turn it back on under **Layers**.
|
|
196
|
+
|
|
197
|
+
Folding is a drawing decision, never an analytical one. A page reachable only through your footer is still
|
|
198
|
+
reachable, and sitelines will not call it an orphan just because the footer is hidden.
|
|
199
|
+
|
|
200
|
+
### Every page is live, at any width
|
|
201
|
+
|
|
202
|
+

|
|
203
|
+
|
|
204
|
+
Click a card to open it: a real preview at mobile, tablet, or desktop width, every exit with its editable
|
|
205
|
+
label and destination, every entrance, and a notes field. Previews come straight off disk, so you do not
|
|
206
|
+
need a dev server running.
|
|
207
|
+
|
|
208
|
+
The dots down each side of a card are ports, and each wire terminates on the exact dot that represents it.
|
|
209
|
+
Hover any dot or wire for the control's real text, both routes, how it navigates, and its `file:line`.
|
|
210
|
+
|
|
211
|
+
### Changes queue instead of landing
|
|
212
|
+
|
|
213
|
+

|
|
214
|
+
|
|
215
|
+
| Do this | Get this |
|
|
216
|
+
| --- | --- |
|
|
217
|
+
| Edit an exit's text field | A queued rename |
|
|
218
|
+
| Change an exit's destination | A queued retarget |
|
|
219
|
+
| Click the ✕ on an exit | A queued removal |
|
|
220
|
+
| **+ Link**, then click a destination | A queued new control |
|
|
221
|
+
| **+ Page** | A queued new page, optionally linked from the selected one |
|
|
222
|
+
|
|
223
|
+
The map previews the result immediately: queued links draw in amber, and a proposed page appears as a
|
|
224
|
+
dashed card. The queue lives in `.sitelines/edits.json` and is the only thing sitelines writes.
|
|
225
|
+
|
|
226
|
+
### Both themes, because it sits next to your editor
|
|
227
|
+
|
|
228
|
+

|
|
229
|
+
|
|
230
|
+
Follows `prefers-color-scheme` and remembers a manual override. Every text color clears WCAG AA in both.
|
|
231
|
+
|
|
232
|
+
### Views are yours to define
|
|
233
|
+
|
|
234
|
+
The base view, **everything**, always shows every page and never takes a rule. Filter from it and sitelines
|
|
235
|
+
starts a new view rather than quietly turning your one complete picture into a partial one. Press **+** on
|
|
236
|
+
the tab bar to name a view, or write one into `.sitelines/views.json` and it appears:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"active": "all",
|
|
241
|
+
"views": [
|
|
242
|
+
{ "id": "all", "label": "everything", "base": true, "include": [], "exclude": [] },
|
|
243
|
+
{ "id": "docs", "label": "docs only", "include": ["/docs/**"], "exclude": [] },
|
|
244
|
+
{ "id": "app", "label": "no legal", "include": [], "exclude": ["/legal/**"] }
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Patterns: `/docs/**` (subtree), `/docs/` (exact), `/docs/*` (one level), `*report*` (substring).
|
|
250
|
+
|
|
251
|
+
### Directories collapse
|
|
252
|
+
|
|
253
|
+
Pages sharing a top-level directory sit on a labelled backdrop. Click the label to fold the whole directory
|
|
254
|
+
into one card: internal links disappear, and the directory keeps one wire per neighbour labelled `N links`,
|
|
255
|
+
with every underlying control in its tooltip.
|
|
256
|
+
|
|
257
|
+
### Keyboard
|
|
258
|
+
|
|
259
|
+
| | |
|
|
260
|
+
| --- | --- |
|
|
261
|
+
| Drag background / wheel | Pan / zoom |
|
|
262
|
+
| `f` | Fit everything on screen |
|
|
263
|
+
| `/` | Focus the filter |
|
|
264
|
+
| `1` `2` `3` | Switch views |
|
|
265
|
+
| `e` | New link from the selected page |
|
|
266
|
+
| `Escape` | Cancel whatever is in progress |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## What the scanner reads
|
|
271
|
+
|
|
272
|
+
| Source | How the label is found |
|
|
273
|
+
| --- | --- |
|
|
274
|
+
| `<a href>` | The link text |
|
|
275
|
+
| `<button>` / `<div>` with `onclick="location.href=…"`, `data-href`, `data-nav`, `formaction` | The element text |
|
|
276
|
+
| `<form action>` | `POST form` |
|
|
277
|
+
| `<meta http-equiv=refresh>` | `meta refresh` |
|
|
278
|
+
| `location.href` / `.assign` / `.replace`, `window.open`, `router.push` / `.replace`, `navigate()` | The nearest `getElementById` or `querySelector` above the call |
|
|
279
|
+
|
|
280
|
+
Pages are `*.html` files. If a project has none, sitelines falls back to framework page files under
|
|
281
|
+
`pages/`, `routes/`, or `app/`.
|
|
282
|
+
|
|
283
|
+
It is static analysis, so it reads what is in the source. A destination assembled at runtime from a template
|
|
284
|
+
string is not something a scanner can resolve, and sitelines skips it rather than guessing.
|
|
285
|
+
|
|
286
|
+
## Previews
|
|
287
|
+
|
|
288
|
+
Two defaults worth knowing:
|
|
289
|
+
|
|
290
|
+
- **Page JavaScript is off.** Preview iframes get an empty `sandbox`. A page that polls or retries a dead
|
|
291
|
+
API will otherwise peg your browser forty iframes over. Markup and CSS still render. Turn it on under
|
|
292
|
+
**Layers → Run page JavaScript**.
|
|
293
|
+
- **Service workers are blocked** inside previews, or your site's own worker caches the preview responses
|
|
294
|
+
and serves them back for every later preview.
|
|
295
|
+
|
|
296
|
+
For pages that need real data or a login, run your own dev server and use the **Open** button on the card.
|
|
297
|
+
|
|
298
|
+
## Performance
|
|
299
|
+
|
|
300
|
+
Large sites stay usable because the viewer refuses to pretend:
|
|
301
|
+
|
|
302
|
+
- At most 12 preview iframes stay mounted, two loading at a time. Offscreen cards unmount.
|
|
303
|
+
- Past 60 pages previews start off, and the viewer says why.
|
|
304
|
+
- Below 0.26 zoom, thumbnails and wire labels leave the render tree instead of compositing a smear.
|
|
305
|
+
|
|
306
|
+
## State
|
|
307
|
+
|
|
308
|
+
sitelines writes one directory next to where you run it:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
.sitelines/
|
|
312
|
+
├── flow.json the scan: pages, links, issues, plus your card positions and notes
|
|
313
|
+
├── views.json your views and their rules
|
|
314
|
+
└── edits.json the change queue, including applied history
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
Add it to `.gitignore` if it is yours alone, or commit it to share the layout and notes with your team.
|
|
318
|
+
Nothing outside this directory is ever written.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## Contributing
|
|
323
|
+
|
|
324
|
+
Issues and pull requests are welcome.
|
|
325
|
+
|
|
326
|
+
- **No dependencies.** Everything is Node's standard library and platform web APIs. A PR adding a runtime
|
|
327
|
+
dependency needs a strong reason.
|
|
328
|
+
- **No build step.** `viewer/` is HTML, CSS, and ES modules served as written.
|
|
329
|
+
- **Read `DESIGN.md` before touching `viewer/style.css`.** It is the authority on tokens, shape, type,
|
|
330
|
+
motion, and what each color means.
|
|
331
|
+
- Test against `examples/demo-site` and against something with more than 40 pages. Density is where this
|
|
332
|
+
tool succeeds or fails.
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// sitelines CLI - scan a site's navigation, then browse and edit it in a map.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const pkgRoot = path.join(here, '..');
|
|
10
|
+
const scripts = path.join(pkgRoot, 'scripts');
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
const cmd = argv[0];
|
|
13
|
+
const rest = argv.slice(1);
|
|
14
|
+
|
|
15
|
+
const version = (() => {
|
|
16
|
+
try { return JSON.parse(fs.readFileSync(path.join(pkgRoot, 'package.json'), 'utf8')).version; }
|
|
17
|
+
catch { return '0.0.0'; }
|
|
18
|
+
})();
|
|
19
|
+
|
|
20
|
+
const HELP = `sitelines ${version} - map, audit, and edit a site's navigation
|
|
21
|
+
|
|
22
|
+
sitelines scan [--root DIR] [--out DIR]
|
|
23
|
+
Read every page and every link between them. Writes .sitelines/flow.json.
|
|
24
|
+
--root defaults to the first of public, site, www, static, src/pages,
|
|
25
|
+
src/routes, src/app, app/pages, pages, app, docs that exists.
|
|
26
|
+
|
|
27
|
+
sitelines serve [--root DIR] [--out DIR] [--port N] [--open]
|
|
28
|
+
Open the map in a browser. Requires a scan first.
|
|
29
|
+
|
|
30
|
+
sitelines open [--root DIR] [--port N]
|
|
31
|
+
Scan, then serve, then open the browser. The usual entry point.
|
|
32
|
+
|
|
33
|
+
sitelines demo [--dir sitelines-demo] [--port N]
|
|
34
|
+
Copy the bundled 20-page example site into ./sitelines-demo, map it, and
|
|
35
|
+
open it. Nothing outside that directory is touched. Use this to see what
|
|
36
|
+
sitelines does before pointing it at your own project.
|
|
37
|
+
|
|
38
|
+
sitelines skill install [--global|--project] [--dir PATH] [--force]
|
|
39
|
+
sitelines skill uninstall [--global|--project] [--dir PATH]
|
|
40
|
+
Install sitelines as a Claude Code skill so /sitelines works and Claude can
|
|
41
|
+
apply the changes you queue in the map. --global is ~/.claude/skills,
|
|
42
|
+
--project is ./.claude/skills. Defaults to --global.
|
|
43
|
+
|
|
44
|
+
sitelines agents [install|uninstall] [--dir PATH]
|
|
45
|
+
Same instructions, as an AGENTS.md block in your project root. Codex,
|
|
46
|
+
opencode, Cursor, Zed and Gemini CLI read that file, so this is how those
|
|
47
|
+
agents learn to apply your queued changes. Appends to an existing
|
|
48
|
+
AGENTS.md rather than overwriting it.
|
|
49
|
+
|
|
50
|
+
sitelines version
|
|
51
|
+
sitelines help
|
|
52
|
+
|
|
53
|
+
State lives in .sitelines/ next to where you run it. Add it to .gitignore, or
|
|
54
|
+
commit it to share the map's layout and notes with your team.`;
|
|
55
|
+
|
|
56
|
+
function run(file, args, opts = {}) {
|
|
57
|
+
const r = spawnSync(process.execPath, [path.join(scripts, file), ...args], { stdio: 'inherit', ...opts });
|
|
58
|
+
if (r.error) { console.error(`sitelines: ${r.error.message}`); process.exit(1); }
|
|
59
|
+
return r.status ?? 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function flagValue(args, name, fallback) {
|
|
63
|
+
const i = args.indexOf(`--${name}`);
|
|
64
|
+
return i > -1 && args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : fallback;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function openBrowser(url) {
|
|
68
|
+
const cmds = process.platform === 'win32'
|
|
69
|
+
? ['cmd', ['/c', 'start', '', url]]
|
|
70
|
+
: process.platform === 'darwin'
|
|
71
|
+
? ['open', [url]]
|
|
72
|
+
: ['xdg-open', [url]];
|
|
73
|
+
try { spawn(cmds[0], cmds[1], { stdio: 'ignore', detached: true }).unref(); }
|
|
74
|
+
catch { /* no browser here; the URL is already printed */ }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
switch (cmd) {
|
|
78
|
+
case 'scan':
|
|
79
|
+
process.exit(run('scan.mjs', rest));
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'serve': {
|
|
83
|
+
const wantOpen = rest.includes('--open');
|
|
84
|
+
const args = rest.filter((a) => a !== '--open');
|
|
85
|
+
if (wantOpen) setTimeout(() => openBrowser(`http://localhost:${flagValue(args, 'port', '4370')}`), 700);
|
|
86
|
+
process.exit(run('serve.mjs', args));
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
case 'open': {
|
|
91
|
+
const code = run('scan.mjs', rest);
|
|
92
|
+
if (code !== 0) process.exit(code);
|
|
93
|
+
setTimeout(() => openBrowser(`http://localhost:${flagValue(rest, 'port', '4370')}`), 700);
|
|
94
|
+
process.exit(run('serve.mjs', rest));
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
case 'demo': {
|
|
99
|
+
const dest = path.resolve(process.cwd(), flagValue(rest, 'dir', 'sitelines-demo'));
|
|
100
|
+
const src = path.join(pkgRoot, 'examples', 'demo-site');
|
|
101
|
+
if (!fs.existsSync(src)) { console.error(`sitelines: the bundled example is missing at ${src}`); process.exit(1); }
|
|
102
|
+
if (fs.existsSync(dest) && fs.readdirSync(dest).length) {
|
|
103
|
+
console.log(`sitelines: reusing the existing ${path.basename(dest)}/`);
|
|
104
|
+
} else {
|
|
105
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
106
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
107
|
+
console.log(`sitelines: copied the example site to ${path.basename(dest)}/ (20 pages)`);
|
|
108
|
+
}
|
|
109
|
+
const port = flagValue(rest, 'port', '4370');
|
|
110
|
+
// keep the demo's state inside the demo, so it never mixes with the map of
|
|
111
|
+
// whatever real project the user runs this from
|
|
112
|
+
const args = ['--root', dest, '--out', path.join(dest, '.sitelines'), '--port', port];
|
|
113
|
+
const code = run('scan.mjs', args);
|
|
114
|
+
if (code !== 0) process.exit(code);
|
|
115
|
+
console.log('sitelines: this example has deliberate faults - 2 dead links, 3 orphans, a dead end, and a page 4 clicks deep');
|
|
116
|
+
setTimeout(() => openBrowser(`http://localhost:${port}`), 700);
|
|
117
|
+
process.exit(run('serve.mjs', args));
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
case 'skill': {
|
|
122
|
+
const sub = rest[0];
|
|
123
|
+
const args = rest.slice(1);
|
|
124
|
+
if (sub === 'install') process.exit(run('install-skill.mjs', args));
|
|
125
|
+
if (sub === 'uninstall') process.exit(run('install-skill.mjs', ['--uninstall', ...args]));
|
|
126
|
+
console.error('sitelines: expected `sitelines skill install` or `sitelines skill uninstall`');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
case 'agents': {
|
|
132
|
+
const sub = rest[0] === 'uninstall' ? ['--uninstall'] : [];
|
|
133
|
+
const args = rest.filter((a) => a !== 'install' && a !== 'uninstall');
|
|
134
|
+
process.exit(run('install-skill.mjs', ['--agents', ...sub, ...args]));
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case 'version':
|
|
139
|
+
case '--version':
|
|
140
|
+
case '-v':
|
|
141
|
+
console.log(version);
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case undefined:
|
|
145
|
+
case 'help':
|
|
146
|
+
case '--help':
|
|
147
|
+
case '-h':
|
|
148
|
+
console.log(HELP);
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
default:
|
|
152
|
+
console.error(`sitelines: unknown command "${cmd}"\n`);
|
|
153
|
+
console.log(HELP);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>About | Meridian</title>
|
|
7
|
+
<link rel="stylesheet" href="../assets/site.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="nav">
|
|
11
|
+
<a class="logo" href="/">Meridian</a>
|
|
12
|
+
<nav>
|
|
13
|
+
<a href="/product/">Product</a>
|
|
14
|
+
<a href="/docs/">Docs</a>
|
|
15
|
+
<a href="/pricing/">Pricing</a>
|
|
16
|
+
</nav>
|
|
17
|
+
<a class="cta" href="/signup/">Start free</a>
|
|
18
|
+
</header>
|
|
19
|
+
<main>
|
|
20
|
+
<h1>About</h1>
|
|
21
|
+
<p class="lead">Six people, one product, no roadmap theatre.</p>
|
|
22
|
+
<p><a href="/blog/">Read the blog</a> and <a href="/contact/">get in touch</a>.</p>
|
|
23
|
+
</main>
|
|
24
|
+
<footer class="foot">
|
|
25
|
+
<a href="/legal/privacy/">Privacy</a>
|
|
26
|
+
<a href="/legal/terms/">Terms</a>
|
|
27
|
+
<a href="https://github.com/">GitHub</a>
|
|
28
|
+
</footer>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|