@nakedev/nextjs-fsd 0.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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +165 -0
  3. package/bin/nextjs-fsd.js +2 -0
  4. package/dist/commands/add.js +182 -0
  5. package/dist/commands/config.js +53 -0
  6. package/dist/commands/generate.js +403 -0
  7. package/dist/commands/init.js +229 -0
  8. package/dist/index.js +291 -0
  9. package/dist/prompts.js +38 -0
  10. package/dist/types.js +5 -0
  11. package/dist/utils/config.js +86 -0
  12. package/dist/utils/copy.js +87 -0
  13. package/dist/utils/naming.js +76 -0
  14. package/dist/utils/project.js +288 -0
  15. package/dist/utils/render.js +66 -0
  16. package/dist/utils/version.js +23 -0
  17. package/package.json +66 -0
  18. package/templates/add/auth/auth-errors.ts.hbs +22 -0
  19. package/templates/add/auth/index.ts.hbs +4 -0
  20. package/templates/add/auth/login-form.tsx.hbs +48 -0
  21. package/templates/add/auth/login-index.ts.hbs +1 -0
  22. package/templates/add/auth/login-page.tsx.hbs +18 -0
  23. package/templates/add/auth/require-session.ts.hbs +29 -0
  24. package/templates/add/auth/session.ts.hbs +75 -0
  25. package/templates/add/errors/access-token.ts.hbs +19 -0
  26. package/templates/add/errors/api-error.ts.hbs +69 -0
  27. package/templates/add/errors/client.test.ts.hbs +61 -0
  28. package/templates/add/errors/client.ts.hbs +91 -0
  29. package/templates/add/errors/config-index.ts.hbs +1 -0
  30. package/templates/add/errors/env.ts.hbs +3 -0
  31. package/templates/add/errors/error-catalog.ts.hbs +19 -0
  32. package/templates/add/errors/error-resolver.ts.hbs +30 -0
  33. package/templates/add/errors/form-error.tsx.hbs +50 -0
  34. package/templates/add/errors/index.ts.hbs +5 -0
  35. package/templates/add/errors/providers.tsx.hbs +14 -0
  36. package/templates/add/errors/query-client.ts.hbs +25 -0
  37. package/templates/generate/layout/layout.tsx.hbs +20 -0
  38. package/templates/generate/layout/route.tsx.hbs +1 -0
  39. package/templates/generate/page/content.tsx.hbs +17 -0
  40. package/templates/generate/page/errors.ts.hbs +22 -0
  41. package/templates/generate/page/index.ts.hbs +1 -0
  42. package/templates/generate/page/page.tsx.hbs +22 -0
  43. package/templates/generate/page/route.tsx.hbs +3 -0
  44. package/templates/generate/slice/api.ts.hbs +42 -0
  45. package/templates/generate/slice/errors.ts.hbs +22 -0
  46. package/templates/generate/slice/index.ts.hbs +15 -0
  47. package/templates/generate/slice/lib.ts.hbs +4 -0
  48. package/templates/generate/slice/model.ts.hbs +10 -0
  49. package/templates/generate/slice/ui.tsx.hbs +17 -0
  50. package/templates/init/agents-section.md.hbs +25 -0
  51. package/templates/init/claude.md.hbs +1 -0
  52. package/templates/init/components.json.hbs +21 -0
  53. package/templates/init/eslint.fsd.mjs.hbs +91 -0
  54. package/templates/init/fsd.md.hbs +133 -0
  55. package/templates/init/globals.css.hbs +31 -0
  56. package/templates/init/skill.md.hbs +167 -0
  57. package/templates/init/steiger.config.ts.hbs +28 -0
@@ -0,0 +1,91 @@
1
+ // The FSD import boundary, as ESLint sees it: one file at a time, in your
2
+ // editor, on every save.
3
+ //
4
+ // Not a replacement for `steiger ./{{srcDir}}` and not redundant with it — the
5
+ // two answer different questions at different moments. A single file can show
6
+ // that this import points the wrong way; only a whole-tree pass can show that
7
+ // a slice has no references at all, that a layer is sliced too finely, or that
8
+ // a segment is named after its type instead of its purpose. ESLint catches the
9
+ // mistake as you type it; steiger catches the shape the codebase drifted into.
10
+ //
11
+ // Zero new dependencies: `no-restricted-imports` is a core rule, and the layer
12
+ // order is the whole of the boundary. Imports point downwards only —
13
+ // _app -> _pages -> widgets -> features -> entities -> shared — and a slice is
14
+ // always entered through its index.ts.
15
+
16
+ const LAYERS = ["_app", "_pages", "widgets", "features", "entities", "shared"];
17
+
18
+ // Layers divided into slices. `_app` and `shared` hold segments instead, so
19
+ // they have no sibling slices to keep apart and no index.ts to enter through.
20
+ const SLICED = ["_pages", "widgets", "features", "entities"];
21
+
22
+ /**
23
+ * Anything below a slice's own directory, from anywhere.
24
+ *
25
+ * One segment past the layer is the slice itself, which is exactly what
26
+ * another layer should import; two or more is its insides, which nothing
27
+ * should. That includes a file reaching into its OWN slice through the alias —
28
+ * inside a slice the import is relative, which is what keeps a slice movable.
29
+ */
30
+ const insideSlice = {
31
+ group: SLICED.map((layer) => `{{alias}}/${layer}/*/**`),
32
+ message: 'reaches inside a slice — import it through its index.ts, e.g. "{{alias}}/features/<slice>".',
33
+ };
34
+
35
+ /**
36
+ * One `no-restricted-imports` entry per group of files, never two.
37
+ *
38
+ * Flat config REPLACES a rule's options when a later block matches the same
39
+ * file; it does not merge them. So every pattern that applies to a layer has
40
+ * to live in that layer's single block — splitting them across blocks silently
41
+ * disables all but the last one to match, and the config still looks right.
42
+ */
43
+ function boundaryFor(files, patterns) {
44
+ return {
45
+ files,
46
+ rules: { "no-restricted-imports": ["error", { patterns: [...patterns, insideSlice] }] },
47
+ };
48
+ }
49
+
50
+ const layerRules = LAYERS.map((layer) => {
51
+ const patterns = [];
52
+
53
+ const higher = LAYERS.slice(0, LAYERS.indexOf(layer));
54
+ if (higher.length > 0) {
55
+ patterns.push({
56
+ group: higher.map((name) => `{{alias}}/${name}/**`),
57
+ message:
58
+ `imports point downwards only, and this is a higher layer than ${layer}. ` +
59
+ "Move the shared code down a layer instead of reaching up.",
60
+ });
61
+ }
62
+
63
+ if (SLICED.includes(layer)) {
64
+ patterns.push({
65
+ group: [`{{alias}}/${layer}/*`],
66
+ message:
67
+ "two slices on the same layer never import each other. Move the shared code down a layer, " +
68
+ "or use a relative path if it really belongs to this slice.",
69
+ });
70
+ }
71
+
72
+ return boundaryFor([`{{srcDir}}/${layer}/**/*.{ts,tsx}`], patterns);
73
+ });
74
+
75
+ const fsdBoundary = [
76
+ ...layerRules,
77
+ // {{appDir}}/ is routing. A route file re-exports a page and its metadata;
78
+ // composing features straight into a route is how the routing layer starts
79
+ // growing logic that belongs in a slice.
80
+ boundaryFor(
81
+ ["{{appDir}}/**/*.{ts,tsx}"],
82
+ [
83
+ {
84
+ group: ["{{alias}}/widgets/**", "{{alias}}/features/**", "{{alias}}/entities/**"],
85
+ message: "{{appDir}}/ composes pages, not features — put it in a _pages slice and re-export that.",
86
+ },
87
+ ]
88
+ ),
89
+ ];
90
+
91
+ export default fsdBoundary;
@@ -0,0 +1,133 @@
1
+ # Feature-Sliced Design in this project
2
+
3
+ > Generated by `nextjs-fsd init`. Edit freely — this file is the convention,
4
+ > not a lock file. Keep it in step with the code; a doc describing the
5
+ > previous shape is worse than no doc.
6
+
7
+ ## Layout
8
+
9
+ ```text
10
+ {{appDir}}/ # Next.js App Router — routing only
11
+ ├── layout.tsx # composition root: fonts, <Providers>, global CSS
12
+ └── <route>/page.tsx # re-exports an FSD page + its metadata, nothing else
13
+ {{srcDir}}/
14
+ ├── _app/ # FSD app layer — providers/, styles/
15
+ ├── _pages/<page>/ # one slice per route: ui/, index.ts (public API)
16
+ ├── features/<slice>/ # a whole user action, once two pages need it
17
+ ├── entities/<slice>/ # a business object, once two features need it
18
+ └── shared/ # infrastructure only — api/, auth/, config/, lib/, ui/
19
+ ```
20
+
21
+ The FSD `app` and `pages` layers are named `_app` and `_pages` because
22
+ Next.js owns those names at the root.
23
+
24
+ **Add layers when a second consumer actually appears, not up front.**
25
+ Duplicating a component across two pages is cheaper to undo than an
26
+ `entities/` layer built for a reuse that never happens. FSD v2.1 discourages
27
+ `widgets/` — UI blocks carry user-flow logic, which makes the
28
+ widget/feature boundary arbitrary — so reach for `features/` first.
29
+
30
+ ## Import boundary
31
+
32
+ Imports point downwards only: `_app → _pages → widgets → features → entities
33
+ → shared`. Two slices on the same layer never import each other, and a slice
34
+ is always imported through its `index.ts`:
35
+
36
+ ```tsx
37
+ import { LoginForm } from "{{alias}}/_pages/login"; // ✅ public API
38
+ import { LoginForm } from "{{alias}}/_pages/login/ui/login-form"; // ❌ reaches inside
39
+ ```
40
+
41
+ If two pages need the same thing, that is the signal it was never
42
+ page-scoped: move it down a layer instead of importing sideways. Two copies
43
+ in two pages while you are still unsure is the cheaper mistake.
44
+
45
+ ## Two linters, on purpose
46
+
47
+ `{{lintCommand}}` runs both, and they are not redundant:
48
+
49
+ | | catches | when |
50
+ |---|---|---|
51
+ | **ESLint** (`eslint.fsd.mjs`) | this import points the wrong way, or reaches past a slice's `index.ts` | as you type, per file, in the editor |
52
+ | **steiger** (`steiger.config.ts`) | a slice with no references, a layer sliced too finely, a segment named after its type, inconsistent naming | on demand, whole tree |
53
+
54
+ A single file is enough to see a bad import, so that check belongs where you
55
+ get it instantly. Nothing in one file can show that a slice has no consumers
56
+ or that a layer has thirty slices — that needs the whole tree, which is what
57
+ steiger walks.
58
+
59
+ `eslint.fsd.mjs` has no dependencies of its own: the boundary is expressed
60
+ with the core `no-restricted-imports` rule, and the layer order above is the
61
+ whole of it. One gotcha if you edit it — flat config **replaces** a rule's
62
+ options when a later block matches the same file instead of merging them, so
63
+ every pattern for a layer has to stay in that layer's single block. Split them
64
+ across two blocks and the earlier one silently stops applying.
65
+
66
+ When steiger says *"this slice has only one reference — consider merging
67
+ them"*, that is the "add layers when a second consumer appears" rule talking,
68
+ not a false positive. It is configured as a **warning** rather than an error,
69
+ because every slice has one consumer on the day it is created and failing the
70
+ build for that teaches people to delete the rule instead of the slice. Read it
71
+ anyway: a slice that stays at one consumer for good probably belongs inside
72
+ that consumer.
73
+
74
+ ## `{{appDir}}/` stays thin
75
+
76
+ A route file re-exports an FSD page and nothing else:
77
+
78
+ ```tsx
79
+ // {{appDir}}/login/page.tsx
80
+ export { LoginPage as default, metadata } from "{{alias}}/_pages/login";
81
+ ```
82
+
83
+ Export `metadata` alongside the component — a route file that only
84
+ re-exports `default` silently drops the page's title.
85
+
86
+ ## Segments
87
+
88
+ A slice gets only the segments it has code for. `ui/` alone is the common
89
+ case; add `model/` (state, hooks), `api/` (requests) or `lib/` (helpers) when
90
+ that slice actually has them. An empty segment folder is noise.
91
+
92
+ Adding one later is the normal path, not a rewrite — re-run the same command
93
+ with the extra segment and it writes only what is missing, appending the new
94
+ exports to the slice's `index.ts` and leaving every existing file untouched:
95
+
96
+ ```bash
97
+ nextjs-fsd generate slice features checkout --segments ui # ui/ only
98
+ nextjs-fsd generate slice features checkout --segments ui,model # adds model/
99
+ ```
100
+
101
+ ```text
102
+ {{srcDir}}/_pages/<page>/
103
+ ├── ui/<page>-page.tsx # server component + `metadata`
104
+ ├── ui/<thing>.tsx # "use client" only on the leaves that need it
105
+ └── index.ts # public API — the only thing {{appDir}}/ imports
106
+ ```
107
+
108
+ ## Server vs. client components
109
+
110
+ Default to a Server Component. `"use client"` goes on the leaf that needs
111
+ `useState`/`useEffect`/events — a page component stays a server component
112
+ while its form does not.
113
+
114
+ ## Generating
115
+
116
+ ```bash
117
+ nextjs-fsd generate page <name> # slice + {{appDir}}/<name>/page.tsx
118
+ nextjs-fsd generate slice features <name> # a feature slice
119
+ nextjs-fsd generate layout admin # _app/layouts + {{appDir}}/(admin)/layout.tsx
120
+ nextjs-fsd add error-handling # shared/api: ApiError, catalogs, client
121
+ nextjs-fsd add auth # shared/auth: session, guard, login page
122
+ ```
123
+
124
+ Re-running a generate command extends what is there instead of refusing, so
125
+ `--errors` or `--client` on a page built bare earlier just adds the file. It
126
+ never rewrites one you already have — and it will not add a second route file
127
+ for a page that is already routed from somewhere else, because two `page.tsx`
128
+ resolving to the same URL is a Next.js build error.
129
+
130
+ Layouts live in `_app/layouts`, not `_pages`: a layout is not one route's
131
+ content, it is what several routes have in common, and `{{appDir}}/(group)/layout.tsx`
132
+ re-exports it the same way a route file re-exports a page. `shadcn add <name>`
133
+ writes into `{{srcDir}}/shared/ui` — `components.json` is already aimed there.
@@ -0,0 +1,31 @@
1
+ @import "tailwindcss";
2
+
3
+ /* This file lives in the FSD app layer, not next to the routes, so name the
4
+ trees Tailwind has to scan for class names explicitly instead of relying on
5
+ where auto-detection decides the project root is. */
6
+ @source "{{cssSourceApp}}";
7
+ @source "{{cssSourceSrc}}";
8
+
9
+ @custom-variant dark (&:is(.dark *));
10
+
11
+ @theme inline {
12
+ --color-background: var(--background);
13
+ --color-foreground: var(--foreground);
14
+ }
15
+
16
+ :root {
17
+ --background: oklch(1 0 0);
18
+ --foreground: oklch(0.145 0 0);
19
+ }
20
+
21
+ .dark {
22
+ --background: oklch(0.145 0 0);
23
+ --foreground: oklch(0.985 0 0);
24
+ }
25
+
26
+ @layer base {
27
+ body {
28
+ background-color: var(--color-background);
29
+ color: var(--color-foreground);
30
+ }
31
+ }
@@ -0,0 +1,167 @@
1
+ ---
2
+ name: nextjs-fsd
3
+ description: >
4
+ Use whenever the user asks to add, create, scaffold, or wire up a new page,
5
+ screen, route, feature, entity, layout, or shared route shell in THIS
6
+ project — including indirect requests such as "add a settings screen", "let
7
+ staff approve loans", "put the dashboard behind login", or "show a proper
8
+ message when the API fails". This project is organized with Feature-Sliced
9
+ Design and has a nextjs-fsd.config.json at its root; check for that file
10
+ before assuming this applies. Also use when adding API error handling or
11
+ authentication for the first time. Do NOT use for business logic or markup
12
+ inside a file the CLI already generated, for styling, for a bug fix or
13
+ refactor, or for a project without nextjs-fsd.config.json.
14
+ ---
15
+
16
+ # nextjs-fsd
17
+
18
+ Use this skill for the FSD surface only: which directory a new thing goes in,
19
+ and how it is imported. Once a slice exists, its content — markup, business
20
+ rules, request shapes — is the engineer's work.
21
+
22
+ Create slices with the CLI rather than by hand, so the route file, the
23
+ `index.ts` public API, the `"use client"` placement, and the segment layout
24
+ stay consistent. Hand-written slices are exactly what the two linters then
25
+ report.
26
+
27
+ This file is emitted at `.claude/skills/nextjs-fsd/SKILL.md`. Agents that do
28
+ not load `.claude/skills` should follow the root `AGENTS.md`, which points at
29
+ [docs/fsd.md](../../../docs/fsd.md) — the fuller convention, and the source of
30
+ truth if this file and it ever disagree.
31
+
32
+ **FSD methodology questions are not this skill's job.** Whether a thing should
33
+ be an entity at all, when to extract from a page, cross-import and `@x`
34
+ patterns, how strict to be — that is the `feature-sliced-design` skill
35
+ (fsd.how). This one only covers driving the generator in this repo.
36
+
37
+ ## When to use this skill
38
+
39
+ - A new page, screen or route is requested, even without the word "page".
40
+ - A new reusable feature or business object is requested, even without the
41
+ words "feature" or "entity".
42
+ - Several routes need shared chrome (a sidebar, an auth card) → a layout.
43
+ - The project needs API error handling or auth for the first time.
44
+ - An existing slice needs a segment it does not have yet (state, requests,
45
+ helpers, an error catalog).
46
+
47
+ Examples that all use this skill:
48
+
49
+ - "add a settings screen" → `generate page settings`
50
+ - "the dashboard should require login" → `generate page dashboard --auth`
51
+ - "admin pages need a shared sidebar" → `generate layout admin`
52
+ - "loans need to be reusable across screens" → `generate slice entities loan`
53
+ - "show a real message when saving fails" → `add error-handling`, then
54
+ `--errors` on the slice that owns the failing call
55
+
56
+ ## When not to use it
57
+
58
+ - Writing the actual markup, state, or request body inside a generated file
59
+ - Styling, Tailwind theme edits, or shadcn component customisation
60
+ - Bug fixes, refactors, or dependency work
61
+ - Deciding whether a layer *should* exist — that is the FSD skill
62
+ - Any repository without nextjs-fsd.config.json at its root
63
+
64
+ ## Before running the CLI
65
+
66
+ 1. Read `nextjs-fsd.config.json`. `features.errorHandling` and `features.auth`
67
+ decide which flags are even legal, `appDir` says whether routes live in
68
+ `app/` or `src/app/`, and `locale` says which language generated copy is
69
+ written in.
70
+ 2. Look for the slice first. `{{srcDir}}/_pages/`, `{{srcDir}}/features/`,
71
+ `{{srcDir}}/entities/` — a name that already exists means you are extending,
72
+ not creating, and the flags differ.
73
+ 3. Check `{{appDir}}/` for an existing route before choosing `--route`. A page
74
+ routed from a group is not where the default would look.
75
+ 4. Confirm the work is on a branch you may commit to (`git status -sb`) and
76
+ preserve unrelated changes.
77
+
78
+ ## Commands
79
+
80
+ Run from the project root. **Pass every value as a flag** — an omitted value
81
+ opens a prompt, and in a non-interactive shell that exits 1 without writing
82
+ anything.
83
+
84
+ ~~~bash
85
+ nextjs-fsd generate page <name> [--route <path>] [--no-route] [--client] [--auth] [--errors] [--defaults]
86
+ nextjs-fsd generate slice <features|entities|widgets> <name> [--segments ui,model,api,lib] [--errors] [--defaults]
87
+ nextjs-fsd generate layout <name> [--route <path>] [--no-route] [--defaults]
88
+ nextjs-fsd add error-handling [-y] [--no-install]
89
+ nextjs-fsd add auth [-y] [--no-install]
90
+ nextjs-fsd config show
91
+ ~~~
92
+
93
+ `--defaults` answers every remaining question, so scripted and agent use needs
94
+ `--defaults` (or the full set of flags) on every generate command.
95
+
96
+ Feature gates, which fail loudly rather than generating something broken:
97
+
98
+ - `--auth` requires `features.auth` → run `add auth` first
99
+ - `--errors` and the `api` segment require `features.errorHandling` →
100
+ run `add error-handling` first
101
+ - `add auth` installs error handling itself if it is missing; do not run both
102
+
103
+ ## Extending, not rewriting
104
+
105
+ **Re-run the same command to add to something that exists.** Do not hand-write
106
+ the file, and do not delete the slice to regenerate it.
107
+
108
+ ~~~bash
109
+ nextjs-fsd generate slice features checkout --segments ui # ui/ only
110
+ nextjs-fsd generate slice features checkout --segments ui,model # adds model/
111
+ nextjs-fsd generate page dashboard --errors --defaults # adds the catalog
112
+ ~~~
113
+
114
+ Only the missing files are written; new exports are appended to the slice's
115
+ `index.ts`; every existing file is left exactly as it is. If there is nothing
116
+ to add, the command says so and writes nothing — that is the expected answer,
117
+ not a failure to work around.
118
+
119
+ Two consequences worth knowing:
120
+
121
+ - The page component is an existing file, so a leaf added later is not rendered
122
+ by anything yet. The command prints the one import line to add; add it.
123
+ - A page already routed from elsewhere does not get a second route file. Two
124
+ `page.tsx` resolving to one URL is a Next.js build error.
125
+
126
+ ## Contracts the generated code relies on
127
+
128
+ Breaking one of these is silent — the build passes and the app is wrong.
129
+
130
+ - **A route file re-exports the page *and* its `metadata`.** `export { X as
131
+ default } from "..."` alone drops the page title with no error.
132
+ - **`"use client"` goes on the leaf, not the page.** A page component stays a
133
+ server component; state, effects and events live in a `ui/<thing>.tsx`.
134
+ - **Imports point downwards, and a slice is entered through its `index.ts`.**
135
+ `{{alias}}/features/x`, never `{{alias}}/features/x/ui/x`. Inside a slice the
136
+ import is relative.
137
+ - **Never render `error.message` as UI copy.** Add the code to the domain's own
138
+ catalog (`model/<name>-errors.ts`) and let the fallback name the action that
139
+ failed. A backend message is written for a log.
140
+ - **One catalog per domain, never one global map.** `generate ... --errors`
141
+ creates it in the right place.
142
+ - **The access token stays in the module variable** in
143
+ `{{srcDir}}/shared/auth/access-token.ts`. Never move it to `localStorage`, and
144
+ never remove the single-flight guard in `shared/api/client.ts` — a backend
145
+ that rotates refresh tokens logs the user out mid-session without it.
146
+ - **`{{appDir}}/` composes pages, not features.** ESLint enforces this one.
147
+
148
+ ## Verification
149
+
150
+ After generating and filling in a slice:
151
+
152
+ ~~~bash
153
+ {{lintCommand}} # eslint (per-file boundary) + steiger (whole-tree)
154
+ {{packageManager}} run build
155
+ ~~~
156
+
157
+ ESLint reports a wrong-way or slice-internal import at the file. steiger
158
+ reports what one file cannot show. `fsd/insignificant-slice` on a brand-new
159
+ slice means nothing imports it yet — either wire it up or inline it back into
160
+ its single consumer; it is the linter working, not a false positive.
161
+
162
+ Where the CLI wrote a `client.test.ts`, run the project's test command too. Do
163
+ not delete that test to make a change pass: the single-flight 401 refresh and
164
+ the no-refresh-on-`/auth/*` rule are the two things in this codebase that fail
165
+ silently in a browser.
166
+
167
+ Report the exact output, and say plainly which generated TODOs are still TODOs.
@@ -0,0 +1,28 @@
1
+ import fsd from "@feature-sliced/steiger-plugin";
2
+ import { defineConfig } from "steiger";
3
+
4
+ export default defineConfig([
5
+ ...fsd.configs.recommended,
6
+ {
7
+ // `app/` and `pages/` are Next.js' own directory names, so the FSD layers
8
+ // take an `_` prefix. Scoped off, not global — a typo in a future layer
9
+ // name still gets flagged.
10
+ files: ["./{{srcDir}}/_app/**", "./{{srcDir}}/_pages/**"],
11
+ rules: { "fsd/typo-in-layer-name": "off" },
12
+ },
13
+ {
14
+ // The Next.js FSD convention names this segment `providers`.
15
+ files: ["./{{srcDir}}/_app/providers/**"],
16
+ rules: { "fsd/segments-by-purpose": "off" },
17
+ },
18
+ {
19
+ // A warning, not an error, because at default severity this fails `lint`
20
+ // on the structure FSD's own guidance calls the normal starting point: a
21
+ // slice extracted for its first consumer, before the second one exists.
22
+ // The message is still worth reading — a slice that stays at one consumer
23
+ // for good probably belongs inside it — but a fresh slice failing CI
24
+ // teaches people to delete the linter rather than the slice.
25
+ files: ["./{{srcDir}}/**"],
26
+ rules: { "fsd/insignificant-slice": "warn" },
27
+ },
28
+ ]);