cursedbelt 4.0.0 → 4.1.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursedbelt",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "description": "The React design system of the cursedbelt split — components, styles, theme. cursedbelt-core below it; server tier in cursedbelt-server.",
@@ -45,7 +45,7 @@
45
45
  "e2e:resize-grip": "playwright test --config e2e/playwright.config.ts resize-grip-rail.spec.ts",
46
46
  "e2e:touch": "playwright test --config e2e/playwright.config.ts --project=touch",
47
47
  "done:serial": "bun run typecheck && bun run lint && bun run test && bun run build",
48
- "paths": "bun ../../tools/check-paths.ts .",
48
+ "paths": "bun run scripts/paths.ts",
49
49
  "verify": "bun run scripts/verify.ts",
50
50
  "prepublishOnly": "bun run verify",
51
51
  "styles:areas": "bun run scripts/generateAreaStyles.ts"
@@ -291,6 +291,24 @@
291
291
  "source": "./src/react/components/SelectField.tsx",
292
292
  "import": "./dist/react/components/SelectField.js"
293
293
  },
294
+ "./react/list-selection": {
295
+ "types": "./dist/react/hooks/useListSelection.d.ts",
296
+ "bun": "./src/react/hooks/useListSelection.ts",
297
+ "source": "./src/react/hooks/useListSelection.ts",
298
+ "import": "./dist/react/hooks/useListSelection.js"
299
+ },
300
+ "./react/table-sort": {
301
+ "types": "./dist/react/hooks/useTableSort.d.ts",
302
+ "bun": "./src/react/hooks/useTableSort.ts",
303
+ "source": "./src/react/hooks/useTableSort.ts",
304
+ "import": "./dist/react/hooks/useTableSort.js"
305
+ },
306
+ "./react/multi-select-field": {
307
+ "types": "./dist/react/components/MultiSelectField.d.ts",
308
+ "bun": "./src/react/components/MultiSelectField.tsx",
309
+ "source": "./src/react/components/MultiSelectField.tsx",
310
+ "import": "./dist/react/components/MultiSelectField.js"
311
+ },
294
312
  "./react/sharing": {
295
313
  "types": "./dist/react/sharing/index.d.ts",
296
314
  "bun": "./src/react/sharing/index.ts",
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * `bun run paths` — the generation's whole-tree laws, run over THIS repo's tree.
4
+ *
5
+ * {@link TOOLS} is the list: `check-paths.ts` (the one law — exactly one file in a generation
6
+ * may contain a literal path) and `check-doc-citations.ts` (every `docs/<name>.md` this repo
7
+ * points a reader at must exist, with the sections it names). Both are generation-scoped rules
8
+ * a repo proves about ITSELF — rule 3, a repo's gate proves that repo — both are located the
9
+ * same way, and both have to fail this step rather than be skipped.
10
+ *
11
+ * ## 🔴 Why this is a wrapper and not `bun ../../tools/check-paths.ts .`
12
+ *
13
+ * That spelling assumes this checkout sits at a fixed depth under the generation root, and a
14
+ * WORKTREE never does: the working agreement puts worktrees outside every generation by
15
+ * construction, under the machine-wide worktrees root. Measured 2026-09-17 in a worktree,
16
+ * with no changes to any script — `error: Module not found "../../tools/check-paths.ts"`,
17
+ * and the gate stops on its FIRST step. Seventeen packages spelled it that way, and the failure
18
+ * mode is not the error — it is the workaround: an agent that cannot run its repo's gate moves
19
+ * back into the shared checkout another agent is holding, or lands without a green gate — and
20
+ * the second one is invisible afterwards.
21
+ *
22
+ * {@link findForgeRoot} walks up for `forge.env`, the same marker `tools/check-paths.ts` and
23
+ * the runner both use, so the three cannot disagree. {@link viaWorktree} is the worktree case:
24
+ * a worktree's `.git` is a FILE pointing at the primary checkout's git directory, so the
25
+ * primary is discoverable with no environment variable and no guess.
26
+ *
27
+ * 🔴 **No generation, no pass.** Answering "fine" because the check could not be located is how
28
+ * a rule stops being enforced without anybody deciding to stop enforcing it — so every failure
29
+ * to locate exits 1, and says which of the two walks came back empty.
30
+ *
31
+ * `tools/check-paths.ts` is what keeps this spelling honest across the roster: a repo whose
32
+ * `paths` script reaches a tool through a relative `../` module path fails the whole-generation
33
+ * run, which is the check that this whole file is the fix for.
34
+ */
35
+ import { spawnSync } from "node:child_process";
36
+ import { existsSync } from "node:fs";
37
+ import { dirname, join, resolve } from "node:path";
38
+ import { fileURLToPath } from "node:url";
39
+
40
+ /** The generation's tools this repo runs over its own tree, in the order they should fail. */
41
+ const TOOLS = ["check-paths.ts", "check-doc-citations.ts"];
42
+
43
+ /** How far up either walk looks before giving up — a bound, so a misuse is `null` in ms. */
44
+ const MAX_HOPS = 16;
45
+
46
+ /** Walk up from `from` for a directory containing `marker`, or `null`. */
47
+ function walkUp(from: string, marker: string): string | null {
48
+ let dir = resolve(from);
49
+ for (let hops = 0; hops < MAX_HOPS; hops++) {
50
+ if (existsSync(join(dir, marker))) return dir;
51
+ const up = dirname(dir);
52
+ if (up === dir) return null;
53
+ dir = up;
54
+ }
55
+ return null;
56
+ }
57
+
58
+ /** The directory holding `forge.env` above `from`, or `null` when that is not inside a generation. */
59
+ function findForgeRoot(from: string): string | null {
60
+ return walkUp(from, "forge.env");
61
+ }
62
+
63
+ /** The generation above the checkout this WORKTREE was cut from, or `null` on anything unexpected. */
64
+ function viaWorktree(from: string): string | null {
65
+ try {
66
+ const git = spawnSync("git", ["rev-parse", "--path-format=absolute", "--git-common-dir"], {
67
+ cwd: from,
68
+ encoding: "utf8",
69
+ timeout: 5_000,
70
+ });
71
+ if (git.status !== 0) return null;
72
+ const gitDir = (git.stdout ?? "").trim();
73
+ if (!gitDir || !gitDir.startsWith("/")) return null;
74
+ // `<primary>/.git` → `<primary>`, then the ordinary walk up.
75
+ return findForgeRoot(dirname(gitDir));
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
80
+
81
+ const here = dirname(fileURLToPath(import.meta.url));
82
+ /** This checkout — the nearest ancestor holding a `package.json`, so it is right in a worktree. */
83
+ const repo = walkUp(here, "package.json");
84
+ const root = findForgeRoot(here) ?? viaWorktree(here);
85
+
86
+ if (!repo) {
87
+ console.error(`✗ no package.json above ${here}, so there is no repo to check.`);
88
+ process.exit(1);
89
+ }
90
+ if (!root) {
91
+ console.error("✗ this checkout is not inside a generation and is not a worktree of one, so");
92
+ console.error(` tools/{${TOOLS.join(",")}} cannot be located. Run it from a checkout under a`);
93
+ console.error(" generation, or from a worktree cut from one — a skipped check is not a passed one.");
94
+ process.exit(1);
95
+ }
96
+
97
+ for (const name of TOOLS) {
98
+ const tool = join(root, "tools", name);
99
+ if (!existsSync(tool)) {
100
+ console.error(`✗ ${tool} does not exist. The generation at ${root} has no ${name},`);
101
+ console.error(" so this repo cannot prove it obeys that law. That is a failure, not a skip.");
102
+ process.exit(1);
103
+ }
104
+ const result = spawnSync("bun", [tool, repo, ...process.argv.slice(2)], { stdio: "inherit" });
105
+ if ((result.status ?? 1) !== 0) process.exit(result.status ?? 1);
106
+ }
@@ -44,7 +44,7 @@ describe('MarkdownDoc', () => {
44
44
 
45
45
  test('javascript: and unknown-scheme links are neutralized to plain text', () => {
46
46
  const { container } = render(
47
- <MarkdownDoc markdown={'[bad](javascript:alert(1)) and [weird](vbscript:x) and [ok](docs/x.md)'} />,
47
+ <MarkdownDoc markdown={'[bad](javascript:alert(1)) and [weird](vbscript:x) and [ok](docs/x.md)'} />, // check-doc-citations:ignore markdown fixture for the link sanitiser
48
48
  );
49
49
  const anchors = Array.from(container.querySelectorAll('a'));
50
50
  expect(anchors).toHaveLength(1);