@nurix/etna 0.19.78 → 0.19.79

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "728af89f1e8e",
2
+ "fingerprint": "354c269e4457",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.10.4",
4
+ "version": "3.10.5",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -15,5 +15,5 @@
15
15
  "docs/memory/main.md",
16
16
  ".mcp.json"
17
17
  ],
18
- "digest": "4b1ec8d430ab"
18
+ "digest": "3614b17652aa"
19
19
  }
@@ -11,6 +11,16 @@ Tailwind v4 + shadcn-based UI conventions for **web** apps — styling, the `@nu
11
11
  - Tokens: CSS vars in `:root, :root[data-theme="light"]` and `:root[data-theme="dark"]` (both specificity 0,2,0 — armor a later bare `:root` can't defeat), consumed as semantic Tailwind tokens (`bg-card`, `text-foreground`). `globals.css` carries the active `@custom-variant dark` line; the theme attribute is `data-theme`. The library bundles `class-variance-authority`, `clsx`, `tailwind-merge` — don't re-add them.
12
12
  - Spacing in 2/4/6/8; `object-cover`; `truncate`; color through tokens — **never** hardcoded hex in components.
13
13
 
14
+ ## Component Search Order
15
+
16
+ **A component already exists until the search proves otherwise** — building one is the last step, never the first instinct, and a primitive the library ships is never re-implemented. Search in order: **`@nurix/components`** (its `exports` keys are the available subpaths) → **the app's own `src/components/`**, which in a monorepo is the consuming package's pool → **the route folders**, where a near-match is a misfiled generic to promote rather than duplicate.
17
+
18
+ **A new component lands in `src/components/` on first use, not on second.** Waiting for reuse to promote it fails in practice: nobody notices the second use, so the promotion never happens and the component sits where the next session will not look. Findability is the point of the pool, so one consumer earns a place in it.
19
+
20
+ **The exception is mechanical — a component importing from a specific route folder belongs to that route folder.** That import is what domain-coupled means, and it is decidable the moment the file is written, where consumer count cannot classify anything until a second use exists. A pool component that starts importing a route has drifted and moves down.
21
+
22
+ **Pay the pollution as you go, never as scheduled cleanup:** re-read the pool before adding — something differing from an existing component by one prop is a prop, not a component — and group the pool by kind once it passes roughly fifteen flat files.
23
+
14
24
  ## TypeScript
15
25
 
16
26
  Every package compiles under `strict: true` and `noUncheckedIndexedAccess: true`.
@@ -1,6 +1,29 @@
1
1
  # General Standards
2
2
 
3
- Runtime versions, package policy, the API response envelope, and GitHub Actions pins — the workspace baseline.
3
+ Runtime versions, package policy, source layout, the API response envelope, and GitHub Actions pins — the workspace baseline.
4
+
5
+ ## Source Layout
6
+
7
+ How a package's `src/` is laid out. A single-runtime package drops `server/` and `shared/`.
8
+
9
+ | Folder | Intent — what it protects | Belongs here when |
10
+ | --- | --- | --- |
11
+ | `app/` | The frame that survives navigation — one router, one auth gate, one shell. The property is **instance count, not importance**; without it every route reinvents the chrome, and the shell becomes where "general" code hides. | Mounted once, present on every page |
12
+ | `surfaces/` | An address maps to one deletable folder holding its page, panels, client and private `lib/`. **One folder per route outranks file count** — a one-file route keeps its folder, because the value is that the folder list *is* the route list. | A routable destination |
13
+ | `components/` | UI shared across the app, **and the known place anyone looks before building** — the second job is the load-bearing one, because a component nobody finds gets rebuilt and the app grows three of it that disagree. | A component importing no route folder |
14
+ | `lib/` | The client's non-visual work — API client, session state, ids, timestamps, hooks. | Non-React logic only the client uses |
15
+ | `shared/` | The contracts and domain logic both runtimes read, so the halves cannot drift; without it the contract is stated twice and diverges on the first change nobody propagates. | Client **and** server both import it |
16
+ | `server/` | The other runtime as a peer in the same package — what lets a contract be a file both sides import instead of a shape both sides guess. | Runs on the server runtime |
17
+
18
+ **Split by role, not by feature** — a flat `src/leads/ src/accounts/…` trades one level of depth for unbounded root breadth, and the top-level scan is what keeps a repo legible. The container **bounds** rather than classifies, so `routes/` is an equally valid name for `surfaces/` — use the word the codebase already speaks.
19
+
20
+ **Never `core/`** — its membership test collapses to "is it important?", making it the queue for files nobody could place; a bare `lib/` holding everything non-visual fails the same way, which is what `shared/` prevents. A route's own nested `lib/` is private and is **never** hoisted, and **dependencies point down** — a shared folder importing one route folder is a defect.
21
+
22
+ **A folder earns its place at ~4+ files, or by being a boundary somebody could delete whole.** Below that, inline it: a folder around one file is a longer filename, and a folder holding only another folder is cascade with nothing at it. Density — not depth, not root breadth — is what makes a tree read as sprawling. The boundary clause splits the one-file cases: a one-file route folder stays, a one-file component folder goes.
23
+
24
+ **Never judge dead code by a name grep** — a relative import never contains its own parent's path, so `grep "x/brand"` cannot see `from "./brand/Detail"`, and every folder imported only by its parent reads as dead. Test by transitive reachability from the real entry points, resolving specifiers as the bundler does (`.ts`, `.tsx`, `.css`, `/index.ts`; strip `?raw`/`?url`; map a `.js` specifier onto its `.ts` source), running the test tree as a **second, separate** entry set — reachable from tests alone means a suite pinning code nothing ships, and both go together. Ambient `.d.ts` files enter through tsconfig `include`, so a sweep always reports them dead. A retired mechanism spans more than source — grep the build config for the same concept before calling a removal complete. **Unreachable ≠ unwanted**: a routed surface rendering dummy data is reachable and still not product, and retiring it removes a route, which is a product decision.
25
+
26
+ **Reshape an existing tree largest-lever-first**, verifying typecheck plus the full suite between steps: purge unreachable files → collapse single-occupancy and pass-through folders → push domain-coupled components down → name the shared layer (the only step that adds a top-level name) → optionally add a path alias, which makes depth invisible at the import site and dissolves most "flatten it" instincts. **Delete and promote before you move** — a folder whose name misdescribes its contents just relocates the lie.
4
27
 
5
28
  ## Runtime Versions
6
29
 
@@ -12,6 +35,8 @@ Runtime versions, package policy, the API response envelope, and GitHub Actions
12
35
 
13
36
  **Only packages in the approved table** — `.claude/hooks/approved-packages.md`, enforced by the PreToolUse install gate, which names the file on a block. The table changes only by explicit user decision.
14
37
 
38
+ Two **dev-only** installs are exempt, each a decision already taken rather than a new one: a scoped companion of an approved package (`@testing-library/react` is on the table, so `-D @testing-library/user-event` needs no row of its own) and `@types/*`. An unscoped or runtime install is always judged against the table.
39
+
15
40
  ## API Response Envelope
16
41
 
17
42
  Success and error share **one** envelope — a cross-service contract clients branch on, so a per-service "improvement" (`success: true`, a boolean `status`, a bare payload) breaks every consumer:
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.5.2",
4
+ "version": "1.5.3",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
8
8
  ".vscode/settings.json"
9
9
  ],
10
10
  "seeds": [],
11
- "digest": "615e0382294b"
11
+ "digest": "4c78e401fa54"
12
12
  }
@@ -1,6 +1,29 @@
1
1
  # General Standards
2
2
 
3
- Runtime versions, package policy, the API response envelope, and GitHub Actions pins — the workspace baseline.
3
+ Runtime versions, package policy, source layout, the API response envelope, and GitHub Actions pins — the workspace baseline.
4
+
5
+ ## Source Layout
6
+
7
+ How a package's `src/` is laid out. A single-runtime package drops `server/` and `shared/`.
8
+
9
+ | Folder | Intent — what it protects | Belongs here when |
10
+ | --- | --- | --- |
11
+ | `app/` | The frame that survives navigation — one router, one auth gate, one shell. The property is **instance count, not importance**; without it every route reinvents the chrome, and the shell becomes where "general" code hides. | Mounted once, present on every page |
12
+ | `surfaces/` | An address maps to one deletable folder holding its page, panels, client and private `lib/`. **One folder per route outranks file count** — a one-file route keeps its folder, because the value is that the folder list *is* the route list. | A routable destination |
13
+ | `components/` | UI shared across the app, **and the known place anyone looks before building** — the second job is the load-bearing one, because a component nobody finds gets rebuilt and the app grows three of it that disagree. | A component importing no route folder |
14
+ | `lib/` | The client's non-visual work — API client, session state, ids, timestamps, hooks. | Non-React logic only the client uses |
15
+ | `shared/` | The contracts and domain logic both runtimes read, so the halves cannot drift; without it the contract is stated twice and diverges on the first change nobody propagates. | Client **and** server both import it |
16
+ | `server/` | The other runtime as a peer in the same package — what lets a contract be a file both sides import instead of a shape both sides guess. | Runs on the server runtime |
17
+
18
+ **Split by role, not by feature** — a flat `src/leads/ src/accounts/…` trades one level of depth for unbounded root breadth, and the top-level scan is what keeps a repo legible. The container **bounds** rather than classifies, so `routes/` is an equally valid name for `surfaces/` — use the word the codebase already speaks.
19
+
20
+ **Never `core/`** — its membership test collapses to "is it important?", making it the queue for files nobody could place; a bare `lib/` holding everything non-visual fails the same way, which is what `shared/` prevents. A route's own nested `lib/` is private and is **never** hoisted, and **dependencies point down** — a shared folder importing one route folder is a defect.
21
+
22
+ **A folder earns its place at ~4+ files, or by being a boundary somebody could delete whole.** Below that, inline it: a folder around one file is a longer filename, and a folder holding only another folder is cascade with nothing at it. Density — not depth, not root breadth — is what makes a tree read as sprawling. The boundary clause splits the one-file cases: a one-file route folder stays, a one-file component folder goes.
23
+
24
+ **Never judge dead code by a name grep** — a relative import never contains its own parent's path, so `grep "x/brand"` cannot see `from "./brand/Detail"`, and every folder imported only by its parent reads as dead. Test by transitive reachability from the real entry points, resolving specifiers as the bundler does (`.ts`, `.tsx`, `.css`, `/index.ts`; strip `?raw`/`?url`; map a `.js` specifier onto its `.ts` source), running the test tree as a **second, separate** entry set — reachable from tests alone means a suite pinning code nothing ships, and both go together. Ambient `.d.ts` files enter through tsconfig `include`, so a sweep always reports them dead. A retired mechanism spans more than source — grep the build config for the same concept before calling a removal complete. **Unreachable ≠ unwanted**: a routed surface rendering dummy data is reachable and still not product, and retiring it removes a route, which is a product decision.
25
+
26
+ **Reshape an existing tree largest-lever-first**, verifying typecheck plus the full suite between steps: purge unreachable files → collapse single-occupancy and pass-through folders → push domain-coupled components down → name the shared layer (the only step that adds a top-level name) → optionally add a path alias, which makes depth invisible at the import site and dissolves most "flatten it" instincts. **Delete and promote before you move** — a folder whose name misdescribes its contents just relocates the lie.
4
27
 
5
28
  ## Runtime Versions
6
29
 
@@ -12,6 +35,8 @@ Runtime versions, package policy, the API response envelope, and GitHub Actions
12
35
 
13
36
  **Only packages in the approved table** — `.claude/hooks/approved-packages.md`, enforced by the PreToolUse install gate, which names the file on a block. The table changes only by explicit user decision.
14
37
 
38
+ Two **dev-only** installs are exempt, each a decision already taken rather than a new one: a scoped companion of an approved package (`@testing-library/react` is on the table, so `-D @testing-library/user-event` needs no row of its own) and `@types/*`. An unscoped or runtime install is always judged against the table.
39
+
15
40
  ## API Response Envelope
16
41
 
17
42
  Success and error share **one** envelope — a cross-service contract clients branch on, so a per-service "improvement" (`success: true`, a boolean `status`, a bare payload) breaks every consumer:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.19.78",
3
+ "version": "0.19.79",
4
4
  "description": "Bootstrap a project with a Nurix harness — copies the harness's CLAUDE.md, skills, agents, rules, and hooks directly into the project from a bundled payload (no marketplace fetch, no network).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,5 +42,5 @@
42
42
  "agent",
43
43
  "plugin"
44
44
  ],
45
- "etnaFingerprint": "728af89f1e8e"
45
+ "etnaFingerprint": "354c269e4457"
46
46
  }