jig-ui 0.15.1 → 0.16.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.0
4
+
5
+ With more than one mode, every barrel names its mode, and none sits in the
6
+ global stylesheet.
7
+
8
+ ### Changed
9
+
10
+ - **`theme.<mode>.css` for every mode, once there are two.** `init` kept
11
+ `theme.css` for the first mode and left it wired into the global stylesheet,
12
+ so a second mode's routes loaded the first mode's tokens globally and their
13
+ own from their layout, and got whichever came last. Now each barrel names its
14
+ mode, the global stylesheet keeps only what every route shares (Tailwind and
15
+ `utilities.css`), and each route's layout imports its own barrel. A project
16
+ with one mode is unchanged: `theme.css`, wired globally.
17
+ - **A project gaining a second mode is migrated.** An unedited `theme.css`
18
+ becomes `theme.<first mode>.css`, and every stylesheet import that resolves
19
+ to it is removed, found by where it points rather than by guessing the file.
20
+ An edited one is left, with a note on where its edits belong. `check` names a
21
+ `theme.css` left beside two modes.
22
+
3
23
  ## 0.15.1
4
24
 
5
25
  ### Fixed
package/README.md CHANGED
@@ -123,6 +123,11 @@ jig.config.json route → mode map
123
123
  .jig/state.json bookkeeping — version, modes, checksums
124
124
  ```
125
125
 
126
+ Declare a second mode and every barrel names its mode instead —
127
+ `theme.editorial.css`, `theme.operator.css` — and none sits in the global
128
+ stylesheet. Each route's layout imports the barrel for its mode; `init` removes
129
+ the import it added and prints which barrel each surface takes.
130
+
126
131
  Nothing you wrote is touched beyond that one import line. Re-running `init`
127
132
  never overwrites a config or brand file you have edited.
128
133
 
@@ -604,7 +609,7 @@ treatment.
604
609
  | `rules/05-copy.md` | Interface text rules |
605
610
  | `<css dir>/jig/brand.*.css` | Identity. One per project. |
606
611
  | `<css dir>/jig/mode.*.css` | Density, scale, rhythm, motion |
607
- | `<css dir>/jig/theme.css` | The barrel — brand + mode. This is what you import. |
612
+ | `<css dir>/jig/theme.css` | The barrel — brand + mode. This is what you import. With more than one mode, `theme.<mode>.css`, one per mode, each imported by its routes' layout. |
608
613
  | `.jig/state.json` | What `init` wrote, with checksums. `update` reads it to leave your edits alone. |
609
614
  | `.jig/specs/`, `.jig/mockups/`, `.jig/critique/` | The design loop's record: what was agreed, what was drawn, what the review found. |
610
615
 
package/dist/index.js CHANGED
@@ -3911,10 +3911,20 @@ function modeWiringProblems(projectRoot) {
3911
3911
  if (modes.length === 0) return [];
3912
3912
  const dir = dirname6(config.brand);
3913
3913
  const rel = (f) => dir === "." ? f : `${dir}/${f}`;
3914
- if (!existsSync12(join22(projectRoot, rel("theme.css")))) return [];
3914
+ const multi = modes.length > 1;
3915
+ const barrelOf = (mode) => multi ? `theme.${mode}.css` : "theme.css";
3916
+ const anyBarrel = ["theme.css", ...modes.map((m) => `theme.${m}.css`)].some((f) => existsSync12(join22(projectRoot, rel(f))));
3917
+ if (!anyBarrel) return [];
3915
3918
  const problems = [];
3916
- modes.forEach((mode, i) => {
3917
- const barrel = rel(i === 0 ? "theme.css" : `theme.${mode}.css`);
3919
+ if (multi && existsSync12(join22(projectRoot, rel("theme.css")))) {
3920
+ problems.push({
3921
+ mode: modes[0],
3922
+ barrel: rel("theme.css"),
3923
+ message: `jig.config.json declares ${modes.length} modes, so each barrel names its mode: ${rel("theme.css")} should be ${rel(barrelOf(modes[0]))}, imported by the layouts that serve it and not by the global stylesheet`
3924
+ });
3925
+ }
3926
+ modes.forEach((mode) => {
3927
+ const barrel = rel(barrelOf(mode));
3918
3928
  const modeFile = `mode.${mode}.css`;
3919
3929
  let body;
3920
3930
  try {
@@ -5084,7 +5094,8 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
5084
5094
  }
5085
5095
  const modeAbsPath = modeAbsPaths[primaryMode];
5086
5096
  const brandFileOnly = brandRelPath.split("/").pop();
5087
- const barrelFor = (mode) => mode === primaryMode ? "theme.css" : `theme.${mode}.css`;
5097
+ const multiMode = declaredModes.length > 1;
5098
+ const barrelFor = (mode) => multiMode ? `theme.${mode}.css` : "theme.css";
5088
5099
  for (const mode of declaredModes) {
5089
5100
  const rel = relKey(...tokensRelDir, barrelFor(mode));
5090
5101
  const abs = join28(opts.projectRoot, ...tokensRelDir, barrelFor(mode));
@@ -5098,6 +5109,20 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
5098
5109
  writeFileSync5(abs, content, "utf8");
5099
5110
  files[rel] = checksum(content);
5100
5111
  }
5112
+ if (multiMode) {
5113
+ const rel = relKey(...tokensRelDir, "theme.css");
5114
+ const abs = join28(opts.projectRoot, ...tokensRelDir, "theme.css");
5115
+ if (existsSync15(abs)) {
5116
+ const state = fileState(opts.projectRoot, abs, rel, initManifest);
5117
+ if (state.tracked && !state.modified) {
5118
+ rmSync2(abs);
5119
+ delete files[rel];
5120
+ log(` Removed ${rel}: with more than one mode, each barrel names its mode (${relKey(...tokensRelDir, barrelFor(primaryMode))}).`);
5121
+ } else {
5122
+ log(` ${rel} has been edited, so it is left alone. With more than one mode it is replaced by ${relKey(...tokensRelDir, barrelFor(primaryMode))}; move your edits there and delete it.`);
5123
+ }
5124
+ }
5125
+ }
5101
5126
  if (detection.cssSystem === "tailwind-v4") {
5102
5127
  const declared = declaredTokenNames(
5103
5128
  [brandAbsPath, modeAbsPath].map((f) => {
@@ -5148,8 +5173,9 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
5148
5173
  }
5149
5174
  }
5150
5175
  }
5151
- if (declaredModes.length > 1) {
5152
- log("\nOne barrel per surface. Import each at that route's entry point:");
5176
+ if (multiMode) {
5177
+ log("\nOne barrel per mode, and none in the global stylesheet. Import each in the layout");
5178
+ log("that serves its routes, and keep Tailwind and utilities.css in the global one:");
5153
5179
  for (const surface of effectiveConfig.surfaces) {
5154
5180
  log(` '${surface.match}' \u2192 ${relKey(...tokensRelDir, barrelFor(surface.mode))}`);
5155
5181
  }
@@ -5174,7 +5200,33 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
5174
5200
  }
5175
5201
  const wireTarget = findWireTarget(detection);
5176
5202
  let wiring;
5177
- if (wireTarget) {
5203
+ if (multiMode) {
5204
+ const bare = join28(opts.projectRoot, ...tokensRelDir, "theme.css");
5205
+ for (const file of detection.cssFiles.filter((f) => !isTokenLayerFile(f))) {
5206
+ const abs = join28(opts.projectRoot, file);
5207
+ try {
5208
+ const before = readFileSync23(abs, "utf8");
5209
+ const after = before.split("\n").filter((l) => {
5210
+ const m = /^\s*@import\s+["']([^"']+)["'];?\s*$/.exec(l);
5211
+ return !(m && !/^[a-z]+:|^\//i.test(m[1]) && resolve2(dirname7(abs), m[1]) === bare);
5212
+ }).join("\n");
5213
+ if (after !== before) {
5214
+ writeFileSync5(abs, after, "utf8");
5215
+ log(`
5216
+ Unwired ${file}: removed its import of ${relKey(...tokensRelDir, "theme.css")}.`);
5217
+ log(` With more than one mode, the global stylesheet imports no barrel: every route would carry that mode's tokens.`);
5218
+ }
5219
+ } catch (err) {
5220
+ log(`
5221
+ Could not edit ${file}: ${err.message}. Remove its import of ${relKey(...tokensRelDir, "theme.css")} by hand.`);
5222
+ }
5223
+ }
5224
+ wiring = {
5225
+ target: null,
5226
+ status: "per-route",
5227
+ snippet: effectiveConfig.surfaces.map((s) => `${s.match} \u2192 ${relKey(...tokensRelDir, barrelFor(s.mode))}`).join("\n")
5228
+ };
5229
+ } else if (wireTarget) {
5178
5230
  const targetAbsDir = dirname7(join28(opts.projectRoot, wireTarget));
5179
5231
  const barrelImport = relativeImportPath(
5180
5232
  targetAbsDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jig-ui",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "A design system for coding agents. 130 numbered UI rules, brand x mode design tokens, and an installer for Claude Code, Codex, Cursor and opencode.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -25,9 +25,11 @@ lives in `src/styles/`, `app/assets/stylesheets/jig/` in a Rails app. Set `brand
25
25
  in `jig.config.json` to put it elsewhere. Projects set up before 0.7.0 keep their
26
26
  `.jig/tokens/` layout; `update` does not move them.
27
27
 
28
- So **never hardcode that path**. `init` writes one barrel per surface, `theme.css`,
29
- which imports the brand and the mode in the right order — import the barrel, and
30
- relocating the layer changes one line instead of every stylesheet:
28
+ So **never hardcode that path**. `init` writes a barrel, `theme.css`, which imports
29
+ the brand and the mode in the right order — import the barrel, and relocating the
30
+ layer changes one line instead of every stylesheet. With more than one mode, every
31
+ barrel names its mode instead (`theme.editorial.css`, `theme.operator.css`); see
32
+ "Multiple modes in one app" below:
31
33
 
32
34
  ```css
33
35
  /* src/styles/jig/theme.css — written by init */
@@ -512,8 +514,14 @@ the same token names with different values, so importing all three into one
512
514
  document leaves only the last — the other two are inert. That is the mechanical
513
515
  reason behind the seam rule below.
514
516
 
515
- **Multiple modes in one app** — scope by route, not by class. Each surface imports its own
516
- barrel — `jig/theme.css` for the primary surface, `jig/theme.<mode>.css` for the
517
- others — at that route's layout or entry level. `init` names them and does not
518
- wire them: which entry point serves `/admin/**` is your routing, which it cannot
519
- see. Do not attempt to nest two modes in one document (`01-modes.md`, seam rules).
517
+ **Multiple modes in one app** — scope by route, not by class. Every barrel names its
518
+ mode, `jig/theme.<mode>.css`, and each route's layout or entry point imports the
519
+ one for its mode. The global stylesheet imports **no** barrel: one imported there
520
+ puts that mode's tokens under every route, so the operator pages carry editorial's
521
+ too and get whichever loaded last. It keeps what every route shares — Tailwind and
522
+ `utilities.css`, whose aliases name tokens rather than values and so hold for any
523
+ mode. When a second mode is declared, `init` replaces `theme.css` with
524
+ `theme.<first mode>.css` and removes the import it had wired, then prints which
525
+ barrel each surface imports; which layout serves `/admin/**` is your routing, which
526
+ it cannot see. Do not attempt to nest two modes in one document (`01-modes.md`,
527
+ seam rules).