trykeyline 0.0.0-stage → 0.1.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/.claude-plugin/marketplace.json +18 -0
- package/LICENSE +21 -0
- package/README.md +117 -2
- package/bin/keyline.mjs +280 -0
- package/package.json +38 -4
- package/plugins/keyline/.claude-plugin/plugin.json +14 -0
- package/plugins/keyline/plugin.json +22 -0
- package/plugins/keyline/skills/keyline/SKILL.md +47 -0
- package/plugins/keyline/skills/keyline/commands/audit.md +30 -0
- package/plugins/keyline/skills/keyline/commands/color.md +7 -0
- package/plugins/keyline/skills/keyline/commands/critique.md +20 -0
- package/plugins/keyline/skills/keyline/commands/depth.md +7 -0
- package/plugins/keyline/skills/keyline/commands/init.md +16 -0
- package/plugins/keyline/skills/keyline/commands/layout.md +7 -0
- package/plugins/keyline/skills/keyline/commands/motion.md +7 -0
- package/plugins/keyline/skills/keyline/commands/patterns.md +7 -0
- package/plugins/keyline/skills/keyline/commands/polish.md +19 -0
- package/plugins/keyline/skills/keyline/commands/refine.md +19 -0
- package/plugins/keyline/skills/keyline/commands/sync.md +15 -0
- package/plugins/keyline/skills/keyline/commands/type.md +7 -0
- package/plugins/keyline/skills/keyline/commands/ux-laws.md +26 -0
- package/plugins/keyline/skills/keyline/domains/color/deriving-a-ramp.md +144 -0
- package/plugins/keyline/skills/keyline/domains/color/guide.md +207 -0
- package/plugins/keyline/skills/keyline/domains/color/status-and-exceptions.md +174 -0
- package/plugins/keyline/skills/keyline/domains/color/theming-mechanics.md +121 -0
- package/plugins/keyline/skills/keyline/domains/depth/elevation-recipes.md +185 -0
- package/plugins/keyline/skills/keyline/domains/depth/fallbacks-and-performance.md +132 -0
- package/plugins/keyline/skills/keyline/domains/depth/guide.md +226 -0
- package/plugins/keyline/skills/keyline/domains/depth/translucent-materials.md +159 -0
- package/plugins/keyline/skills/keyline/domains/layout/forks-gating-and-disclosures.md +201 -0
- package/plugins/keyline/skills/keyline/domains/layout/guide.md +259 -0
- package/plugins/keyline/skills/keyline/domains/layout/measure-and-rhythm.md +193 -0
- package/plugins/keyline/skills/keyline/domains/layout/radius-lines-and-layers.md +122 -0
- package/plugins/keyline/skills/keyline/domains/motion/guards.md +162 -0
- package/plugins/keyline/skills/keyline/domains/motion/guide.md +198 -0
- package/plugins/keyline/skills/keyline/domains/motion/ladder-and-curves.md +139 -0
- package/plugins/keyline/skills/keyline/domains/motion/patterns.md +196 -0
- package/plugins/keyline/skills/keyline/domains/motion/performance.md +121 -0
- package/plugins/keyline/skills/keyline/domains/patterns/actions-and-forms.md +204 -0
- package/plugins/keyline/skills/keyline/domains/patterns/agents-and-live-states.md +111 -0
- package/plugins/keyline/skills/keyline/domains/patterns/collections-and-dashboards.md +131 -0
- package/plugins/keyline/skills/keyline/domains/patterns/copy.md +123 -0
- package/plugins/keyline/skills/keyline/domains/patterns/guide.md +174 -0
- package/plugins/keyline/skills/keyline/domains/patterns/iteration.md +70 -0
- package/plugins/keyline/skills/keyline/domains/type/display-tier.md +108 -0
- package/plugins/keyline/skills/keyline/domains/type/guide.md +227 -0
- package/plugins/keyline/skills/keyline/domains/type/ladders.md +169 -0
- package/plugins/keyline/skills/keyline/domains/type/numerals-and-loading.md +107 -0
- package/plugins/keyline/skills/keyline/domains/type/prose-and-emphasis.md +125 -0
- package/plugins/keyline/skills/keyline/domains/type/type-audit.md +87 -0
- package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +299 -0
- package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +119 -0
- package/plugins/keyline/skills/keyline/glass/color/guide.md +195 -0
- package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +136 -0
- package/plugins/keyline/skills/keyline/glass/color/theming.md +106 -0
- package/plugins/keyline/skills/keyline/glass/depth/assets/GlassPanel.tsx +171 -0
- package/plugins/keyline/skills/keyline/glass/depth/assets/glass.css +322 -0
- package/plugins/keyline/skills/keyline/glass/depth/blur-and-tint.md +50 -0
- package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +87 -0
- package/plugins/keyline/skills/keyline/glass/depth/guide.md +147 -0
- package/plugins/keyline/skills/keyline/glass/depth/performance-and-fallbacks.md +37 -0
- package/plugins/keyline/skills/keyline/glass/layout/adaptive-presentations.md +231 -0
- package/plugins/keyline/skills/keyline/glass/layout/assets/SectionNav.tsx +244 -0
- package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +201 -0
- package/plugins/keyline/skills/keyline/glass/layout/guide.md +259 -0
- package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +134 -0
- package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +103 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/DropMenu.tsx +106 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/motion-presets.ts +210 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/motion-tokens.css +176 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/useReveal.ts +65 -0
- package/plugins/keyline/skills/keyline/glass/motion/assets/useScrollTick.ts +183 -0
- package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +108 -0
- package/plugins/keyline/skills/keyline/glass/motion/guide.md +255 -0
- package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +81 -0
- package/plugins/keyline/skills/keyline/glass/motion/scroll-performance.md +58 -0
- package/plugins/keyline/skills/keyline/glass/motion/shared-element.md +161 -0
- package/plugins/keyline/skills/keyline/glass/motion/sound.md +195 -0
- package/plugins/keyline/skills/keyline/glass/motion/springs.md +51 -0
- package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +268 -0
- package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +416 -0
- package/plugins/keyline/skills/keyline/glass/type/guide.md +220 -0
- package/plugins/keyline/skills/keyline/glass/type/long-form-prose.md +151 -0
- package/plugins/keyline/skills/keyline/glass/type/scale.md +69 -0
- package/plugins/keyline/skills/keyline/glass/type/weight-and-tracking.md +61 -0
- package/plugins/keyline/skills/keyline/scripts/check/css.mjs +112 -0
- package/plugins/keyline/skills/keyline/scripts/check/design.mjs +67 -0
- package/plugins/keyline/skills/keyline/scripts/check/files.mjs +44 -0
- package/plugins/keyline/skills/keyline/scripts/check/markup.mjs +149 -0
- package/plugins/keyline/skills/keyline/scripts/check/rules.mjs +584 -0
- package/plugins/keyline/skills/keyline/scripts/check.mjs +297 -0
- package/plugins/keyline/skills/keyline/scripts/contrast.mjs +153 -0
- package/plugins/keyline/skills/keyline/scripts/scan.mjs +340 -0
- package/plugins/keyline/skills/keyline/system/design-md.md +136 -0
- package/plugins/keyline/skills/keyline/system/detection.md +53 -0
- package/plugins/keyline/skills/keyline/system/guide.md +97 -0
- package/plugins/keyline/skills/keyline/system/presets.md +58 -0
- package/plugins/keyline/skills/keyline/system/roles.md +150 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keyline",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Mihir Modi",
|
|
5
|
+
"url": "https://github.com/Mihirmodi27"
|
|
6
|
+
},
|
|
7
|
+
"metadata": {
|
|
8
|
+
"description": "Keyline: interface design laws for coding agents, applied in your project's own tokens.",
|
|
9
|
+
"version": "0.1.1"
|
|
10
|
+
},
|
|
11
|
+
"plugins": [
|
|
12
|
+
{
|
|
13
|
+
"name": "keyline",
|
|
14
|
+
"source": "./plugins/keyline",
|
|
15
|
+
"description": "One skill with eleven commands — init, sync, critique, audit, type, color, layout, depth, motion, patterns, polish — that applies one set of interface laws through a DESIGN.md written in your project's own names."
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mihir Modi
|
|
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
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Keyline
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Interface design laws for coding agents.
|
|
4
|
+
|
|
5
|
+
Keyline reads your codebase once and writes a `DESIGN.md` that maps your own token names onto its roles and picks a preset for your reader. From then on your agent critiques, audits and fixes type, colour, layout, depth, motion and product patterns through that file: the same laws on every project, your values on yours.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx trykeyline install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The npm package is `trykeyline`, because npm holds `keyline` too close to an older package's name. Everything else, the skill and the commands in your agent, is `keyline`.
|
|
12
|
+
|
|
13
|
+
Then, in your agent:
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
/keyline init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
| Command | Does |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `/keyline init` | Scans the codebase, maps its tokens onto roles, picks a preset, writes `DESIGN.md` with the open issues it found |
|
|
24
|
+
| `/keyline sync` | Reconciles `DESIGN.md` with code that has moved since |
|
|
25
|
+
| `/keyline critique` | A designer's read of one screen: what the reader feels first, what's working, the few changes that matter |
|
|
26
|
+
| `/keyline audit` | Every law checked with evidence, per domain, scored 0–4 |
|
|
27
|
+
| `/keyline type` | Type scale, weights, leading, tracking, emphasis, numerals, font loading |
|
|
28
|
+
| `/keyline color` | Tokens by intent, text tiers by measured contrast, status colours, themes |
|
|
29
|
+
| `/keyline layout` | Measure, spacing that groups, radii, line weights, z-index, breakpoints, empty states |
|
|
30
|
+
| `/keyline depth` | Elevation, shadows, edges, scrims, translucent surfaces and their fallbacks |
|
|
31
|
+
| `/keyline motion` | Durations, curves, springs, open and close, reduced motion, scroll |
|
|
32
|
+
| `/keyline patterns` | Labels, buttons, destructive actions, forms, agent UIs, dashboards |
|
|
33
|
+
| `/keyline polish` | All six domains in order, one reviewable pass each |
|
|
34
|
+
|
|
35
|
+
You don't have to name a command: ask for "tighter spacing" or "a review of the settings page" and the agent picks one.
|
|
36
|
+
|
|
37
|
+
`critique`, `audit`, `init` and `sync` change no code. The refining commands change only what their domain owns, in your token names, and write decisions back to `DESIGN.md` so the next session starts from them.
|
|
38
|
+
|
|
39
|
+
## The detector
|
|
40
|
+
|
|
41
|
+
Twenty-four of the laws can be checked from source alone, with no model and no API key:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npx trykeyline check # human report; exits 1 on errors
|
|
45
|
+
npx trykeyline check --json # every finding, for CI or an agent
|
|
46
|
+
npx trykeyline check --strict # warnings fail the run too
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
It reads CSS, Tailwind classes, inline styles, `<style>` blocks and CSS-in-JS, in React, Vue, Svelte, Astro and plain HTML, and it reads `DESIGN.md` so the verdicts follow your preset and your motion and z-index ladders. A few of the things it catches: `transition: all`; animation with no reduced-motion branch; an outline removed with nothing in its place; `100vh`; `z-index: 9999`; colours typed inline instead of tokens; translucency with no `@supports` fallback; pinch-zoom disabled; uppercase labels and Title Case buttons where the preset says sentence case. `npx trykeyline check --rules` lists all of them, each with the law it comes from.
|
|
50
|
+
|
|
51
|
+
`/keyline audit` runs it first and spends its own reading on what a detector can't see — hierarchy, grouping, whether a box should exist at all.
|
|
52
|
+
|
|
53
|
+
Silence a line with a `keyline-ignore` comment on it or the line above, naming the rule if you like (`/* keyline-ignore type/uppercase */`); a whole file with `keyline-ignore-file`. Leave out paths with `--ignore src/content`.
|
|
54
|
+
|
|
55
|
+
In CI:
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
- run: npx trykeyline check
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Presets
|
|
62
|
+
|
|
63
|
+
The laws hold everywhere. A preset sets the dials — how far the type scale climbs, how much colour is spent, how long motion runs — for what your reader is doing:
|
|
64
|
+
|
|
65
|
+
| Preset | For a reader who is |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `editorial` | Reading: articles, docs, portfolios, changelogs |
|
|
68
|
+
| `product` | Working: dashboards, tools, anything used daily |
|
|
69
|
+
| `expressive` | Being persuaded: landing pages, launches, campaigns |
|
|
70
|
+
| `glass` | Getting [modimihir.com](https://modimihir.com)'s exact system, with drop-in tokens and components |
|
|
71
|
+
|
|
72
|
+
Mixed projects are normal: `product`, with the marketing routes on `expressive`. Any dial can be moved, and the move is recorded in `DESIGN.md` with its reason.
|
|
73
|
+
|
|
74
|
+
## Installing
|
|
75
|
+
|
|
76
|
+
`npx trykeyline install` asks which agent you use and copies the skill into that agent's folder. Flags answer up front:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npx trykeyline install --tool codex,cursor # several agents
|
|
80
|
+
npx trykeyline install --tool claude-code --user # every project on this machine
|
|
81
|
+
npx trykeyline update # refresh every copy already installed
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Agent | `--tool` | This project | Every project (`--user`) |
|
|
85
|
+
|---|---|---|---|
|
|
86
|
+
| Claude Code | `claude-code` | `.claude/skills` | `~/.claude/skills` |
|
|
87
|
+
| Codex | `codex` | `.agents/skills` | `~/.codex/skills` |
|
|
88
|
+
| Cursor | `cursor` | `.agents/skills` | `~/.cursor/skills` |
|
|
89
|
+
| Gemini CLI | `gemini-cli` | `.agents/skills` | `~/.gemini/skills` |
|
|
90
|
+
| GitHub Copilot | `github-copilot` | `.agents/skills` | `~/.copilot/skills` |
|
|
91
|
+
| OpenCode | `opencode` | `.agents/skills` | `~/.config/opencode/skills` |
|
|
92
|
+
| Windsurf | `windsurf` | `.windsurf/skills` | `~/.codeium/windsurf/skills` |
|
|
93
|
+
| Any agent that reads `.agents/skills` | `agents` | `.agents/skills` | `~/.agents/skills` |
|
|
94
|
+
|
|
95
|
+
**Claude Code plugin:**
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
/plugin marketplace add Mihirmodi27/keyline
|
|
99
|
+
/plugin install keyline@keyline
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Claude Code names a plugin's skills after the plugin, so installed this way the command is `/keyline:keyline init`, `/keyline:keyline critique` and so on. Installed with `npx trykeyline install`, it's `/keyline`.
|
|
103
|
+
|
|
104
|
+
**With the [`skills` CLI](https://github.com/vercel-labs/skills):** `npx skills add Mihirmodi27/keyline --agent claude-code`.
|
|
105
|
+
|
|
106
|
+
Or run the installer straight from GitHub: `npx github:Mihirmodi27/keyline install`.
|
|
107
|
+
|
|
108
|
+
### Coming from interface-skills
|
|
109
|
+
|
|
110
|
+
Keyline replaces both interface-skills packages: the adaptable set became the commands, and the Glass package became the `glass` preset. `npx trykeyline install` finds the old skills and offers to remove them, since their descriptions would compete with Keyline's for the same requests. A `DESIGN.md` written by the old setup skill works as it is.
|
|
111
|
+
|
|
112
|
+
## Requirements
|
|
113
|
+
|
|
114
|
+
Node 18 or later, for the installer and for the three scripts the skill runs: the detector, a token inventory and a contrast checker. None of them needs a dependency, a model or an API key.
|
|
115
|
+
|
|
116
|
+
## Licence
|
|
117
|
+
|
|
118
|
+
MIT © Mihir Modi
|
package/bin/keyline.mjs
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Keyline's command line.
|
|
4
|
+
*
|
|
5
|
+
* npx trykeyline install asks which coding agent
|
|
6
|
+
* npx trykeyline install --tool codex,cursor answers up front
|
|
7
|
+
* npx trykeyline install --user for every project on this machine
|
|
8
|
+
* npx trykeyline update refresh every copy already installed
|
|
9
|
+
*
|
|
10
|
+
* Keyline is one skill folder. Each coding agent reads skills from its own
|
|
11
|
+
* folder: Claude Code and Windsurf have their own; Codex, Cursor, Gemini CLI,
|
|
12
|
+
* GitHub Copilot, OpenCode and most others share `.agents/skills` in a project.
|
|
13
|
+
* The folder is always copied, never symlinked, so an agent reads real files,
|
|
14
|
+
* and agents that share a folder share one copy.
|
|
15
|
+
*
|
|
16
|
+
* The skill ships inside this package, so installing copies rather than
|
|
17
|
+
* downloads: it works offline and installs exactly the version npx resolved.
|
|
18
|
+
*/
|
|
19
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { homedir } from "node:os";
|
|
22
|
+
import { join, relative, resolve } from "node:path";
|
|
23
|
+
import { createInterface } from "node:readline/promises";
|
|
24
|
+
|
|
25
|
+
const SOURCE = fileURLToPath(new URL("../plugins/keyline/skills/keyline/", import.meta.url));
|
|
26
|
+
const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
27
|
+
const NAME = "keyline";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Where each agent reads skills from — in a project, and for the whole machine.
|
|
31
|
+
* The ids and folders match the `skills` CLI (vercel-labs/skills), so a skill
|
|
32
|
+
* installed either way lands in the same place.
|
|
33
|
+
*/
|
|
34
|
+
const home = homedir();
|
|
35
|
+
const configHome = process.env.XDG_CONFIG_HOME?.trim() || join(home, ".config");
|
|
36
|
+
const TOOLS = {
|
|
37
|
+
"claude-code": {
|
|
38
|
+
title: "Claude Code",
|
|
39
|
+
project: ".claude/skills",
|
|
40
|
+
global: join(process.env.CLAUDE_CONFIG_DIR?.trim() || join(home, ".claude"), "skills"),
|
|
41
|
+
},
|
|
42
|
+
codex: {
|
|
43
|
+
title: "Codex",
|
|
44
|
+
project: ".agents/skills",
|
|
45
|
+
global: join(process.env.CODEX_HOME?.trim() || join(home, ".codex"), "skills"),
|
|
46
|
+
},
|
|
47
|
+
cursor: { title: "Cursor", project: ".agents/skills", global: join(home, ".cursor", "skills") },
|
|
48
|
+
"gemini-cli": { title: "Gemini CLI", project: ".agents/skills", global: join(home, ".gemini", "skills") },
|
|
49
|
+
"github-copilot": { title: "GitHub Copilot", project: ".agents/skills", global: join(home, ".copilot", "skills") },
|
|
50
|
+
opencode: { title: "OpenCode", project: ".agents/skills", global: join(configHome, "opencode", "skills") },
|
|
51
|
+
windsurf: { title: "Windsurf", project: ".windsurf/skills", global: join(home, ".codeium", "windsurf", "skills") },
|
|
52
|
+
agents: {
|
|
53
|
+
title: "Any other agent that reads .agents/skills",
|
|
54
|
+
project: ".agents/skills",
|
|
55
|
+
global: join(home, ".agents", "skills"),
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const ALIASES = { claude: "claude-code", gemini: "gemini-cli", copilot: "github-copilot", other: "agents" };
|
|
59
|
+
const TOOL_IDS = Object.keys(TOOLS);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Keyline replaces the interface-skills packages. Left installed beside it,
|
|
63
|
+
* their descriptions compete with Keyline's for the same requests, so install
|
|
64
|
+
* offers to remove them.
|
|
65
|
+
*/
|
|
66
|
+
const SUPERSEDED = [
|
|
67
|
+
"design-system-setup",
|
|
68
|
+
"typographic-system",
|
|
69
|
+
"color-and-theming",
|
|
70
|
+
"layout-and-hierarchy",
|
|
71
|
+
"surfaces-and-depth",
|
|
72
|
+
"interface-motion",
|
|
73
|
+
"product-patterns",
|
|
74
|
+
"glass-typography",
|
|
75
|
+
"glass-color",
|
|
76
|
+
"glass-layout",
|
|
77
|
+
"glass-and-depth",
|
|
78
|
+
"glass-motion",
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const USAGE = `Keyline ${version} — interface design laws for coding agents.
|
|
82
|
+
|
|
83
|
+
npx trykeyline install [options] install the skill
|
|
84
|
+
npx trykeyline update [options] refresh every copy already installed
|
|
85
|
+
npx trykeyline check [paths…] the laws that can be checked without a model
|
|
86
|
+
(--json, --strict; see npx trykeyline check --help)
|
|
87
|
+
npx trykeyline tools list the agents it installs for
|
|
88
|
+
|
|
89
|
+
Options:
|
|
90
|
+
-t, --tool <tool[,tool…]> which agent — asked for when not given, Claude Code
|
|
91
|
+
when there's no terminal to ask on. One of:
|
|
92
|
+
${TOOL_IDS.join(", ")}
|
|
93
|
+
--user for every project on this machine, not just this one
|
|
94
|
+
--dir <path> into <path>, whatever the agent
|
|
95
|
+
-f, --force overwrite a copy that's already there
|
|
96
|
+
-h, --help this
|
|
97
|
+
-v, --version the version
|
|
98
|
+
|
|
99
|
+
Then, in your agent: /keyline init`;
|
|
100
|
+
|
|
101
|
+
function die(message) {
|
|
102
|
+
console.error(`keyline: ${message}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toolId(name) {
|
|
107
|
+
const id = ALIASES[name] ?? name;
|
|
108
|
+
if (!TOOLS[id]) die(`no agent called '${name}' — use one of: ${TOOL_IDS.join(", ")}`);
|
|
109
|
+
return id;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ── Arguments ──────────────────────────────────────────────────────────── */
|
|
113
|
+
|
|
114
|
+
const argv = process.argv.slice(2);
|
|
115
|
+
|
|
116
|
+
// `check` is the detector, which lives in the skill so an agent can run it without this CLI.
|
|
117
|
+
if (argv[0] === "check") {
|
|
118
|
+
const { run } = await import(new URL("../plugins/keyline/skills/keyline/scripts/check.mjs", import.meta.url));
|
|
119
|
+
process.exit(run(argv.slice(1)));
|
|
120
|
+
}
|
|
121
|
+
const command = argv[0] && !argv[0].startsWith("-") ? argv.shift() : null;
|
|
122
|
+
let dir = null;
|
|
123
|
+
let user = false;
|
|
124
|
+
let force = false;
|
|
125
|
+
const tools = [];
|
|
126
|
+
|
|
127
|
+
for (let i = 0; i < argv.length; i++) {
|
|
128
|
+
const arg = argv[i];
|
|
129
|
+
const value = () => (arg.includes("=") ? arg.slice(arg.indexOf("=") + 1) : argv[++i]);
|
|
130
|
+
if (arg === "--user") user = true;
|
|
131
|
+
else if (arg === "--dir" || arg.startsWith("--dir=")) {
|
|
132
|
+
const path = value();
|
|
133
|
+
if (!path) die("--dir needs a path");
|
|
134
|
+
dir = resolve(path);
|
|
135
|
+
} else if (["-t", "--tool", "--agent"].includes(arg) || arg.startsWith("--tool=") || arg.startsWith("--agent=")) {
|
|
136
|
+
const v = value();
|
|
137
|
+
if (!v) die("--tool needs an agent name");
|
|
138
|
+
for (const name of v.split(",").map((s) => s.trim()).filter(Boolean)) tools.push(toolId(name));
|
|
139
|
+
} else if (arg === "-f" || arg === "--force") force = true;
|
|
140
|
+
else if (arg === "-h" || arg === "--help") {
|
|
141
|
+
console.log(USAGE);
|
|
142
|
+
process.exit(0);
|
|
143
|
+
} else if (arg === "-v" || arg === "--version") {
|
|
144
|
+
console.log(version);
|
|
145
|
+
process.exit(0);
|
|
146
|
+
} else die(`unknown option: ${arg} (try --help)`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!command) {
|
|
150
|
+
console.log(USAGE);
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
if (command === "tools") {
|
|
154
|
+
for (const id of TOOL_IDS) console.log(`${id.padEnd(16)} ${TOOLS[id].title}`);
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
if (command !== "install" && command !== "update") die(`no command called '${command}' (try --help)`);
|
|
158
|
+
|
|
159
|
+
/* ── Helpers ────────────────────────────────────────────────────────────── */
|
|
160
|
+
|
|
161
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY;
|
|
162
|
+
|
|
163
|
+
async function prompt(question) {
|
|
164
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
165
|
+
try {
|
|
166
|
+
return (await rl.question(question)).trim();
|
|
167
|
+
} catch {
|
|
168
|
+
// Ctrl+C or Ctrl+D at a question: stop quietly, having written nothing yet for this question.
|
|
169
|
+
console.log("\nCancelled.");
|
|
170
|
+
process.exit(130);
|
|
171
|
+
} finally {
|
|
172
|
+
rl.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function askTools() {
|
|
177
|
+
if (!interactive) {
|
|
178
|
+
console.log("No terminal to ask on, so installing for Claude Code. Pass --tool to choose (try `keyline tools`).\n");
|
|
179
|
+
return ["claude-code"];
|
|
180
|
+
}
|
|
181
|
+
console.log("Which agent?\n");
|
|
182
|
+
TOOL_IDS.forEach((id, i) => console.log(` ${i + 1}) ${TOOLS[id].title}`));
|
|
183
|
+
console.log();
|
|
184
|
+
for (;;) {
|
|
185
|
+
const answer = (await prompt("Choose one or more, like 1 or 1,2 [1]: ")) || "1";
|
|
186
|
+
const picks = answer.split(/[\s,]+/).filter(Boolean).map(Number);
|
|
187
|
+
if (picks.length && picks.every((n) => Number.isInteger(n) && n >= 1 && n <= TOOL_IDS.length)) {
|
|
188
|
+
console.log();
|
|
189
|
+
return [...new Set(picks.map((n) => TOOL_IDS[n - 1]))];
|
|
190
|
+
}
|
|
191
|
+
console.log(` Type numbers from 1 to ${TOOL_IDS.length}, separated by commas.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const shown = (path) => (path.startsWith(process.cwd()) ? `./${relative(process.cwd(), path)}` : path.replace(home, "~"));
|
|
196
|
+
|
|
197
|
+
/** The folders to install into, each with the agents that read it. */
|
|
198
|
+
function destinations(ids, global) {
|
|
199
|
+
const byPath = new Map();
|
|
200
|
+
for (const id of ids) {
|
|
201
|
+
const path = global ? TOOLS[id].global : resolve(TOOLS[id].project);
|
|
202
|
+
byPath.set(path, [...(byPath.get(path) ?? []), TOOLS[id].title]);
|
|
203
|
+
}
|
|
204
|
+
// More than two readers only happens on an update sweep of the shared
|
|
205
|
+
// .agents/skills folder, where naming all six says less than naming the folder.
|
|
206
|
+
return [...byPath].map(([path, readers]) => ({
|
|
207
|
+
path,
|
|
208
|
+
label: readers.length > 2 ? `Every agent that reads ${shown(path)}` : readers.join(", "),
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const installedAt = (path) => existsSync(join(path, NAME, "SKILL.md"));
|
|
213
|
+
const supersededAt = (path) => SUPERSEDED.filter((s) => existsSync(join(path, s)));
|
|
214
|
+
|
|
215
|
+
function copyTo(path) {
|
|
216
|
+
const target = join(path, NAME);
|
|
217
|
+
mkdirSync(path, { recursive: true });
|
|
218
|
+
rmSync(target, { recursive: true, force: true });
|
|
219
|
+
cpSync(SOURCE, target, { recursive: true });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function retire(path) {
|
|
223
|
+
const old = supersededAt(path);
|
|
224
|
+
if (old.length === 0) return;
|
|
225
|
+
console.log(` Found interface-skills here, which Keyline replaces: ${old.join(", ")}.`);
|
|
226
|
+
const links = old.filter((s) => lstatSync(join(path, s)).isSymbolicLink());
|
|
227
|
+
if (links.length) console.log(` (${links.join(", ")} ${links.length === 1 ? "is a link" : "are links"}; removing ${links.length === 1 ? "it" : "them"} leaves the target alone.)`);
|
|
228
|
+
if (!interactive) {
|
|
229
|
+
console.log(" Left in place — remove them by hand, or rerun in a terminal, so they don't compete with Keyline.");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const answer = ((await prompt(" Remove them? [Y/n]: ")) || "y").toLowerCase();
|
|
233
|
+
if (!answer.startsWith("y")) return;
|
|
234
|
+
for (const s of old) rmSync(join(path, s), { recursive: true, force: true });
|
|
235
|
+
console.log(` ✓ removed ${old.length}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (!existsSync(join(SOURCE, "SKILL.md"))) die("the package is missing its SKILL.md — reinstall it");
|
|
239
|
+
|
|
240
|
+
/* ── update ─────────────────────────────────────────────────────────────── */
|
|
241
|
+
|
|
242
|
+
if (command === "update") {
|
|
243
|
+
// Every place Keyline is already installed — in this project and on this machine.
|
|
244
|
+
const scopes = dir ? [{ path: dir, label: shown(dir) }] : [...destinations(TOOL_IDS, false), ...destinations(TOOL_IDS, true)];
|
|
245
|
+
const found = scopes.filter((d) => installedAt(d.path));
|
|
246
|
+
if (found.length === 0) {
|
|
247
|
+
console.log("Keyline isn't installed here or for this user — run `npx trykeyline install`.");
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
for (const d of found) {
|
|
251
|
+
copyTo(d.path);
|
|
252
|
+
console.log(`✓ ${d.label} → ${shown(join(d.path, NAME))}`);
|
|
253
|
+
}
|
|
254
|
+
console.log(`\nKeyline ${version}, ${found.length} ${found.length === 1 ? "copy" : "copies"} updated.`);
|
|
255
|
+
process.exit(0);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ── install ────────────────────────────────────────────────────────────── */
|
|
259
|
+
|
|
260
|
+
const dests = dir ? [{ path: dir, label: shown(dir) }] : destinations(tools.length ? tools : await askTools(), user);
|
|
261
|
+
let installed = 0;
|
|
262
|
+
|
|
263
|
+
for (const d of dests) {
|
|
264
|
+
console.log(`${d.label} → ${shown(join(d.path, NAME))}`);
|
|
265
|
+
if (installedAt(d.path) && !force) {
|
|
266
|
+
console.log(" · already installed — `npx trykeyline update` refreshes it, --force overwrites it");
|
|
267
|
+
} else {
|
|
268
|
+
copyTo(d.path);
|
|
269
|
+
console.log(` ✓ keyline ${version}`);
|
|
270
|
+
installed++;
|
|
271
|
+
}
|
|
272
|
+
await retire(d.path);
|
|
273
|
+
console.log();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (installed > 0) {
|
|
277
|
+
console.log("Next, in your agent:");
|
|
278
|
+
console.log(" /keyline init scan this project and write DESIGN.md");
|
|
279
|
+
console.log(" /keyline critique then a designer's read of any screen");
|
|
280
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trykeyline",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Interface design laws for coding agents: one skill that reads your codebase, writes a DESIGN.md in your own token names, then critiques, audits and fixes type, colour, layout, depth, motion and product patterns through it.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"design",
|
|
7
|
+
"design-system",
|
|
8
|
+
"ui",
|
|
9
|
+
"typography",
|
|
10
|
+
"accessibility",
|
|
11
|
+
"agent-skills",
|
|
12
|
+
"claude-code",
|
|
13
|
+
"codex",
|
|
14
|
+
"cursor",
|
|
15
|
+
"skills"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/Mihirmodi27/keyline#readme",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Mihirmodi27/keyline.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Mihir Modi (https://modimihir.com)",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"keyline": "bin/keyline.mjs",
|
|
27
|
+
"trykeyline": "bin/keyline.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin",
|
|
31
|
+
"plugins",
|
|
32
|
+
".claude-plugin"
|
|
33
|
+
],
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=18"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "node --test \"test/**/*.test.mjs\""
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keyline",
|
|
3
|
+
"displayName": "Keyline",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"description": "Interface design laws for coding agents. /keyline init reads your codebase and writes a DESIGN.md in your own token names; critique, audit, type, color, layout, depth, motion, patterns and polish work through it, with dials set by a preset (editorial, product, expressive or glass).",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Mihir Modi",
|
|
8
|
+
"url": "https://modimihir.com"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/Mihirmodi27/keyline#readme",
|
|
11
|
+
"repository": "https://github.com/Mihirmodi27/keyline",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": ["design-system", "design-tokens", "typography", "motion", "colour", "layout", "accessibility"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
|
+
"name": "keyline",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"description": "Interface design laws for coding agents. /keyline init reads your codebase and writes a DESIGN.md in your own token names; critique, audit, type, color, layout, depth, motion, patterns and polish work through it, with dials set by a preset (editorial, product, expressive or glass).",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Mihir Modi",
|
|
8
|
+
"url": "https://modimihir.com"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/Mihirmodi27/keyline#readme",
|
|
11
|
+
"repository": "https://github.com/Mihirmodi27/keyline",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"design-system",
|
|
15
|
+
"design-tokens",
|
|
16
|
+
"typography",
|
|
17
|
+
"motion",
|
|
18
|
+
"colour",
|
|
19
|
+
"layout",
|
|
20
|
+
"accessibility"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keyline
|
|
3
|
+
description: "Design, review and fix interfaces with one set of laws, applied in the project's own tokens and names. Commands: init (write DESIGN.md), sync, critique, audit, type, color, layout, depth, motion, patterns, polish. Use to set up or document a design system, critique or audit a UI, or improve typography, colour, contrast, dark mode, spacing, layout, hierarchy, cards, shadows, glass and translucent surfaces, animation, hover and focus states, UI copy, forms, dashboards, and agent and AI chat interfaces. Also for screens that feel generic, boxy, cluttered or template-made, and for building new UI. Not for backend-only work."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Keyline
|
|
7
|
+
|
|
8
|
+
Keyline gives an agent the judgement of a careful interface designer, as laws it can apply and check. **The laws are the same for every project; the values never are.** A setup pass reads the codebase once and writes `DESIGN.md`, which maps the project's own token names onto Keyline's roles and picks a preset for its reader. Every other command works through that file, in the project's names.
|
|
9
|
+
|
|
10
|
+
Paths in these files are relative to this skill's folder. In shell commands it is written `<keyline>` — substitute the folder this file is in.
|
|
11
|
+
|
|
12
|
+
## Running it
|
|
13
|
+
|
|
14
|
+
`/keyline <command> [target]` — the target is a route, file, directory or component; without one, the command takes the whole project, or the screen the conversation is about.
|
|
15
|
+
|
|
16
|
+
| Command | Does |
|
|
17
|
+
|---|---|
|
|
18
|
+
| `init` | Scans the codebase and writes `DESIGN.md` |
|
|
19
|
+
| `sync` | Reconciles `DESIGN.md` with code that has moved |
|
|
20
|
+
| `critique` | A designer's ranked read of one screen |
|
|
21
|
+
| `audit` | Every law checked with evidence, scored per domain |
|
|
22
|
+
| `type` | Scale, weights, leading, tracking, numerals, fonts |
|
|
23
|
+
| `color` | Tokens, contrast, status colours, themes |
|
|
24
|
+
| `layout` | Measure, spacing, radii, lines, z-index, breakpoints |
|
|
25
|
+
| `depth` | Elevation, shadows, scrims, translucent surfaces |
|
|
26
|
+
| `motion` | Durations, curves, springs, reduced motion |
|
|
27
|
+
| `patterns` | Labels, buttons, forms, dialogs, agent UIs, dashboards |
|
|
28
|
+
| `polish` | All six domains in order, one pass each |
|
|
29
|
+
|
|
30
|
+
**No command named?** Pick the one the request is about, say which, and go: "set up", "document", "extract" → `init`; "review", "what's wrong", "feedback" → `critique`; "check everything", "lint", "score" → `audit`; "font", "headings", "typography" → `type`; "colour", "contrast", "dark mode" → `color`; "spacing", "padding", "alignment", "cramped" → `layout`; "shadow", "card", "glass", "modal" → `depth`; "animate", "transition", "feels janky" → `motion`; "copy", "labels", "form", "dialog" → `patterns`; "make it better", "clean this up", "finish" → `polish`. Building something new runs the relevant domains' `## Building it for a project` sections, in polish order (patterns has none; its nearest is `## Iterating on feedback`). `colour`, `typography`, `spacing`, `animate` and `copy` work as aliases.
|
|
31
|
+
|
|
32
|
+
## Before any command
|
|
33
|
+
|
|
34
|
+
1. **Read `DESIGN.md`** at the project root. `## Profile` gives the preset and any areas on another preset; each domain has its own section; `## Decisions` overrides the preset's defaults and must be respected; `## Open issues` is what's already known.
|
|
35
|
+
2. **No `DESIGN.md`:** for `init`, that's the job. For anything else, run `init` for the domains this command touches — `## Profile` and those domains' sections — do the task, then recommend a full `/keyline init`. Don't refuse.
|
|
36
|
+
3. **Load the command's file**, `commands/<command>.md`. It says which guides to read, and which sections; read them, don't work from memory of them.
|
|
37
|
+
4. **Preset `glass`:** the exact system from modimihir.com. Beside each domain guide, read `glass/<domain>/guide.md` for the exact recipes behind the values in `DESIGN.md`; from its `assets/`, take only the files its `## Assets` list names for what you're changing. There is no `glass/patterns/`: patterns take the dials of the preset nearest the reader.
|
|
38
|
+
|
|
39
|
+
## What every command holds to
|
|
40
|
+
|
|
41
|
+
- **The project's names, always.** Reason in roles (`text.secondary`, `duration.small`), write code in whatever the project calls them. Never rename a project's tokens; never add a second token system beside the first. Where a role you need has no row, reuse a value already doing that job; otherwise add one in the project's naming style, marked `proposed`.
|
|
42
|
+
- **Measure, don't estimate.** Mechanical laws are checked by `<keyline>/scripts/check.mjs`; contrast comes from `<keyline>/scripts/contrast.mjs`; inventories from `<keyline>/scripts/scan.mjs`; counts from the code. A finding carries its evidence: a ratio, a count, a `file:line`.
|
|
43
|
+
- **Laws hold under every preset; dials move between them.** A change that would break a law, or move a dial off the preset, is said out loud and asked about first. When the user agrees, it goes under `## Decisions` with its date and reason.
|
|
44
|
+
- **Keep what the design already chose.** An existing interface keeps its brand colour and gradients wherever they do a job, and its corners stay at least as round. Only a law removes something, and the report names that law: a pass that leaves the screen greyer or boxier than it found it has to say why.
|
|
45
|
+
- **Findings are not fixes.** `init`, `sync`, `critique` and `audit` change no code. The refining commands change only what their domain owns, and name the owning domain for anything else they notice.
|
|
46
|
+
- **Write back.** A decision that changes the system — a new role, a moved dial, a law knowingly broken, a change a law forced that a later session might undo (marked *law*) — goes into `DESIGN.md`, so the next session starts from it: roles and values in the domain's section, decisions under `## Decisions` (format: `system/design-md.md`). Open issues are one line each with evidence; fixed ones come off.
|
|
47
|
+
- **Look at the result.** When the project can be rendered, render it before and after a change and look at both themes, a narrow width, and reduced motion where they apply. Code that reads right can still look wrong.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# `/keyline audit`
|
|
2
|
+
|
|
3
|
+
Every law, checked with evidence, per domain — the full inspection `critique` is the summary of. Findings go into `DESIGN.md` so later sessions start from them.
|
|
4
|
+
|
|
5
|
+
**Read:** `DESIGN.md`, then from every `domains/<domain>/guide.md` (all six unless the user narrows it):
|
|
6
|
+
|
|
7
|
+
- **The checklist** — each law's `### N.` heading and its bold first sentence: `awk 'FNR==1{p=0; print "== " FILENAME} /^## /{p = /^## Laws/} p' <keyline>/domains/*/guide.md | grep -oE '^(== |### ).*|^\*\*([^*]|\*[^*])+\*\*'`
|
|
8
|
+
- **`## Auditing existing code`**, and `## Checklist` where a guide still has one: `awk 'FNR==1{p=0; print "== " FILENAME} /^## /{p = /^## (Auditing|Checklist)/} p' <keyline>/domains/*/guide.md`
|
|
9
|
+
|
|
10
|
+
Read a law's full text where a finding needs it. Under preset `glass`, also each `glass/<domain>/guide.md` checklist (there is no `glass/patterns/`): `awk 'FNR==1{p=0; print "== " FILENAME} /^## /{p = /^## Checklist/} p' <keyline>/glass/*/guide.md`
|
|
11
|
+
|
|
12
|
+
**Steps:**
|
|
13
|
+
|
|
14
|
+
1. **Detector:** `node <keyline>/scripts/check.mjs --root <project root> --json --ignore <prose and example dirs>`. Every finding it returns is evidence already — file, line, law — so start each domain from its findings and spend your reading on what it can't see: hierarchy, grouping, whether a box should exist. Treat a finding as a lead, not a verdict; one you judge wrong goes in the report as such, and gets a `keyline-ignore` comment only if the user agrees.
|
|
15
|
+
2. **Inventory:** `node <keyline>/scripts/scan.mjs . --ignore <prose and example dirs>`.
|
|
16
|
+
3. **Contrast:** every text tier, the focus ring (`--min 3`) and status colours, per theme, with `<keyline>/scripts/contrast.mjs`.
|
|
17
|
+
4. **Per domain, in polish order** — type, colour, layout, depth, motion, patterns — run that guide's audit: its searches, then its evidence per law. Render where the guide asks you to look (widths, themes, reduced motion, keyboard).
|
|
18
|
+
5. **Score each domain** from what you found, the detector's findings and yours together:
|
|
19
|
+
|
|
20
|
+
| Score | Means |
|
|
21
|
+
|---|---|
|
|
22
|
+
| 4 | Every law holds; nothing to report |
|
|
23
|
+
| 3 | Small strays — a handful of one-off values, nothing a reader would notice |
|
|
24
|
+
| 2 | A law broken in a way a reader feels — a hierarchy inversion, a failed contrast tier, a box around everything |
|
|
25
|
+
| 1 | Several laws broken across templates; the domain isn't working as a system |
|
|
26
|
+
| 0 | No system: values typed inline, nothing on a ladder |
|
|
27
|
+
|
|
28
|
+
**Report:** a table of the six domains with score and finding count, then findings grouped by domain, worst first, one line each with evidence — `file:line`, a ratio, a count — and the law, with its UX-law name where one explains it (`commands/ux-laws.md`). Close with the order to fix in: usually the lowest score first, unless a law in another domain blocks it.
|
|
29
|
+
|
|
30
|
+
**Write back:** every finding under `## Open issues` in `DESIGN.md`, replacing entries now fixed.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# `/keyline color`
|
|
2
|
+
|
|
3
|
+
**Guide:** `domains/color/guide.md` · **`DESIGN.md` section:** `## Colour` · **Procedure:** `commands/refine.md` · **Alias:** `colour`
|
|
4
|
+
|
|
5
|
+
Makes colour mean something: tokens named for the job they do rather than their lightness, text tiers chosen by measured contrast and kept strictly descending, a focus colour that's never lost, status colours as foreground and background pairs, colour spent on what needs a person rather than on decoration, and themes that switch without a flash.
|
|
6
|
+
|
|
7
|
+
Typical finds: one grey doing duty as both a border and a caption; a tier that passes 4.5:1 but is indistinguishable from its neighbour; a second accent hue; success green on steps the reader has simply passed (outside an expressive brand's counted exceptions); a gradient that wasn't re-picked for dark. Every ratio comes from `<keyline>/scripts/contrast.mjs`. Deeper references: `deriving-a-ramp.md`, `status-and-exceptions.md`, `theming-mechanics.md`.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# `/keyline critique`
|
|
2
|
+
|
|
3
|
+
A designer's read of one screen, flow or component: what a reader notices first, what's working, and the few changes that would matter most. Judgement, ranked — not a checklist. For every law checked with evidence, use `/keyline audit`.
|
|
4
|
+
|
|
5
|
+
**Read:** `DESIGN.md`. Then the law headings (the `### N.` lines) of all six domain guides — enough to know which laws exist and cite them by number: `grep -n '^### [0-9]' <keyline>/domains/*/guide.md`. Read a law's full text only for the two or three domains where the screen's biggest problems sit, after step 3 has found them: `awk '/^## /{p = /^## Laws/} p' <keyline>/domains/<domain>/guide.md`. A critique ranks a few changes; it doesn't need every law in full, which is what `/keyline audit` is for.
|
|
6
|
+
|
|
7
|
+
**Steps:**
|
|
8
|
+
|
|
9
|
+
1. **Look at it.** Render it if you can — the running app, a preview, a screenshot through whatever browser tool you have — at a desktop and a phone width, in each theme. Read the code too, for what rendering hides (states, focus, reduced motion). If nothing can be rendered, say the critique is from code alone. With a headless Chrome and no other tool, `--screenshot --window-size=1440,900` shoots a page; dark mode needs the page's own theme switch (its `data-theme` or class) set in the URL or DOM, because Chrome's `--blink-settings` flag only gives light reliably.
|
|
10
|
+
2. **Name what the screen is for and who reads it**, in one sentence. Everything below is measured against it.
|
|
11
|
+
3. **Read it as its reader would, in order:** what the eye lands on first, second, third — and whether that order matches what matters. Then how it groups, what competes, what's missing, what's noise.
|
|
12
|
+
4. **Tie each problem to a law**, by domain and number (`layout law 3`), so the fix is a rule rather than a taste. Where a named UX law explains *why* the reader feels it, name that too (`layout law 13, Fitts's law`) — it tells the owner the problem is about people, not preference. The names, and where Keyline holds each, are in `commands/ux-laws.md`.
|
|
13
|
+
|
|
14
|
+
**Report:**
|
|
15
|
+
|
|
16
|
+
- **The read** — two or three sentences: what the screen gets right, and the one thing that most undercuts it.
|
|
17
|
+
- **Changes, most important first** — at most five. Each: what's wrong, as the reader feels it; the law; the fix in a sentence, in the project's names; and the command that makes it (`/keyline layout`).
|
|
18
|
+
- **Keep** — what's working and shouldn't be touched by the fixes.
|
|
19
|
+
|
|
20
|
+
Don't pad to five. Don't list what's fine as a problem to fix.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# `/keyline depth`
|
|
2
|
+
|
|
3
|
+
**Guide:** `domains/depth/guide.md` · **`DESIGN.md` section:** `## Surfaces` · **Procedure:** `commands/refine.md`
|
|
4
|
+
|
|
5
|
+
Makes every surface say where it sits — flat, raised, floating or modal — with each level one bundle of surface, edge, shadow and layer. Shadows from one light, dark themes that lift by lightening, scrims, gradient strokes, and translucency only where it earns its place, legible from its tint alone, with every fallback.
|
|
6
|
+
|
|
7
|
+
Typical finds: a card that should be a section; four shadow recipes for two levels; dark-mode cards that vanish because the shadow was the only cue; blur stacked on blur; text over glass that fails contrast against its worst backdrop; no `@supports` or reduced-transparency fallback. Deeper references: `elevation-recipes.md`, `translucent-materials.md`, `fallbacks-and-performance.md`.
|