jig-ui 0.6.0 → 0.7.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/dist/index.js CHANGED
@@ -2975,7 +2975,7 @@ function check(opts) {
2975
2975
  }
2976
2976
 
2977
2977
  // src/commands/init.ts
2978
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, readFileSync as readFileSync18, writeFileSync as writeFileSync4 } from "fs";
2978
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, readdirSync as readdirSync9, readFileSync as readFileSync18, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
2979
2979
  import { dirname as dirname6, isAbsolute, join as join23, resolve as resolve2, sep as sep3 } from "path";
2980
2980
 
2981
2981
  // src/init/detect.ts
@@ -3547,6 +3547,11 @@ function wireBarrel(absPath, barrelImport) {
3547
3547
  async function init(opts) {
3548
3548
  const log = opts.log ?? ((line) => console.log(line));
3549
3549
  const prompt = opts.prompt ?? defaultPrompt;
3550
+ if (!opts.yes && !opts.prompt && !process.stdin.isTTY) {
3551
+ throw new Error(
3552
+ "'jig init' asks questions and stdin is not a terminal, so it cannot. Re-run with --yes to accept the derived defaults, or run it in a terminal. (To choose the mode without a terminal, write jig.config.json first \u2014 init honours it.)"
3553
+ );
3554
+ }
3550
3555
  const migrateLegacy = async (report, describe) => {
3551
3556
  if (!report.present) return;
3552
3557
  for (const line of describe(report)) log(line);
@@ -3642,7 +3647,24 @@ Brand colour adjusted to clear the 4.5:1 contrast floor: l:${originalL}% \u2192
3642
3647
  // a project holding a legacy token layer must keep it rather than gain a
3643
3648
  // second one somewhere else.
3644
3649
  legacyBrandFile(opts.projectRoot);
3645
- const brandRelPath = configuredBrand.path ?? priorBrand ?? relKey(...defaultTokenDir(detection), brandFileName(projectSlug));
3650
+ let relocateTo = null;
3651
+ const currentBrand = configuredBrand.path ?? priorBrand;
3652
+ if (currentBrand) {
3653
+ const wouldBe = relKey(...defaultTokenDir(detection), currentBrand.split("/").pop());
3654
+ const currentDir = currentBrand.split("/").slice(0, -1).join("/");
3655
+ if (wouldBe !== currentBrand && findWireTarget(detection)) {
3656
+ log(`
3657
+ The token layer is at ${currentDir}/, and this project's layout suggests ${wouldBe.split("/").slice(0, -1).join("/")}/.`);
3658
+ if (opts.yes) {
3659
+ log(" Re-run without --yes to be offered the move, or set `brand` in jig.config.json yourself.");
3660
+ } else {
3661
+ const answer = (await prompt(" Move the token layer there? [y/N]: ")).toLowerCase();
3662
+ if (answer === "y" || answer === "yes") relocateTo = wouldBe;
3663
+ else log(" Left it where it is.");
3664
+ }
3665
+ }
3666
+ }
3667
+ const brandRelPath = relocateTo ?? configuredBrand.path ?? priorBrand ?? relKey(...defaultTokenDir(detection), brandFileName(projectSlug));
3646
3668
  const brandAbsPath = join23(opts.projectRoot, ...brandRelPath.split("/"));
3647
3669
  const tokensRelDir = brandRelPath.split("/").slice(0, -1);
3648
3670
  log(
@@ -3682,6 +3704,18 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
3682
3704
  writeFileSync4(brandAbsPath, content, "utf8");
3683
3705
  files[brandRelPath] = checksum(content);
3684
3706
  }
3707
+ if (relocateTo && configAction !== "written") {
3708
+ try {
3709
+ const existing = JSON.parse(readFileSync18(configAbsPath, "utf8"));
3710
+ const content = `${JSON.stringify({ ...existing, brand: brandRelPath }, null, 2)}
3711
+ `;
3712
+ writeFileSync4(configAbsPath, content, "utf8");
3713
+ files[configRelPath] = checksum(content);
3714
+ log(` Updated ${configRelPath} to point at the new location.`);
3715
+ } catch {
3716
+ log(` Could not update ${configRelPath} \u2014 set \`brand\` to ${brandRelPath} yourself.`);
3717
+ }
3718
+ }
3685
3719
  if (configAction === "written") {
3686
3720
  const config = { brand: brandRelPath, surfaces };
3687
3721
  const content = `${JSON.stringify(config, null, 2)}
@@ -3753,6 +3787,23 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
3753
3787
  }
3754
3788
  }
3755
3789
  writeInitManifest(opts.projectRoot, { version: opts.version, modes: declaredModes, files });
3790
+ if (relocateTo && currentBrand) {
3791
+ const oldDir = currentBrand.split("/").slice(0, -1);
3792
+ const oldAbsDir = join23(opts.projectRoot, ...oldDir);
3793
+ if (existsSync13(oldAbsDir)) {
3794
+ for (const name of readdirSync9(oldAbsDir).filter((f) => f.endsWith(".css"))) {
3795
+ const rel = relKey(...oldDir, name);
3796
+ const abs = join23(oldAbsDir, name);
3797
+ const state = fileState(opts.projectRoot, abs, rel, initManifest);
3798
+ if (!state.tracked || state.modified) {
3799
+ log(` ${rel} was edited \u2014 left in place. Delete it yourself once you have moved anything you need.`);
3800
+ continue;
3801
+ }
3802
+ rmSync2(abs, { force: true });
3803
+ delete files[rel];
3804
+ }
3805
+ }
3806
+ }
3756
3807
  const wireTarget = findWireTarget(detection);
3757
3808
  let wiring;
3758
3809
  if (wireTarget) {
@@ -3763,6 +3814,13 @@ ${configRelPath} already exists and was not created by 'jig init' (or has been e
3763
3814
  );
3764
3815
  const snippet = `@import "${barrelImport}";`;
3765
3816
  try {
3817
+ if (relocateTo && currentBrand) {
3818
+ const abs = join23(opts.projectRoot, wireTarget);
3819
+ const oldDirName = currentBrand.split("/").slice(0, -1).join("/");
3820
+ const before = existsSync13(abs) ? readFileSync18(abs, "utf8") : "";
3821
+ const after = before.split("\n").filter((l) => !(l.includes("@import") && l.includes(oldDirName))).join("\n");
3822
+ if (after !== before) writeFileSync4(abs, after, "utf8");
3823
+ }
3766
3824
  const outcome = wireBarrel(join23(opts.projectRoot, wireTarget), barrelImport);
3767
3825
  wiring = { target: wireTarget, status: outcome.status, snippet };
3768
3826
  const verb = outcome.status === "wired" ? "Wired" : outcome.status === "rewired" ? "Rewired" : "Already present in";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jig-ui",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "A design system for coding agents. 104 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",
@@ -19,13 +19,24 @@ tokens/
19
19
 
20
20
  A surface loads **exactly one brand file and exactly one mode file**.
21
21
 
22
- **Tokens live at `.jig/tokens/`.** That is the only location, in every scope and
23
- every project — `jig install` puts them there, `jig update` refreshes them there,
24
- and nothing relocates them. Import from that path and it stays correct.
22
+ **The token layer's location follows the project.** `init` writes it beside the
23
+ stylesheet it wires and prints the path it chose — `src/styles/jig/` where the CSS
24
+ lives in `src/styles/`, `app/assets/stylesheets/jig/` in a Rails app. Set `brand`
25
+ in `jig.config.json` to put it elsewhere. Projects set up before 0.7.0 keep their
26
+ `.jig/tokens/` layout; `update` does not move them.
25
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:
31
+
32
+ ```css
33
+ /* src/styles/jig/theme.css — written by init */
34
+ @import "./brand.acme.css";
35
+ @import "./mode.operator.css";
36
+ ```
26
37
  ```css
27
- @import ".jig/tokens/brand.acme.css";
28
- @import ".jig/tokens/mode.operator.css";
38
+ /* your stylesheet */
39
+ @import "./jig/theme.css";
29
40
  ```
30
41
 
31
42
  Three separate mode files rather than one file with variants. The trade: a surface cannot switch modes at runtime, and shared values are duplicated across three files. In exchange each surface ships only the tokens it uses, the files are independently readable, and there is no cascade to reason about. For a system where mode is a routing decision rather than a user preference, that is the right trade.
@@ -369,8 +380,7 @@ In dark, elevated surfaces get **lighter**, not shadowed. Border-led elevation s
369
380
 
370
381
  **Plain CSS, any framework**
371
382
  ```css
372
- @import ".jig/tokens/brand.default.css";
373
- @import ".jig/tokens/mode.product.css";
383
+ @import "./jig/theme.css";
374
384
 
375
385
  .card {
376
386
  background: var(--color-bg-raised);