@martinrun/frontend-config 0.1.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Martin Caspersen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # frontend-kit
2
+
3
+ Shared frontend conventions for personal projects. React + TypeScript +
4
+ shadcn/ui + Tailwind.
5
+
6
+ One repo, three distribution channels:
7
+
8
+ - **npm package** `@martinrun/frontend-config` — ESLint, tsconfig, Prettier.
9
+ Updated by Renovate.
10
+ - **shadcn registry** — `DESIGN.md`, the API client, query defaults, theme
11
+ tokens. Updated deliberately with `shadcn add --overwrite`.
12
+ - **Claude Code plugin** — the conventions skill, so agents follow the rules
13
+ without a copy of the doc in every repo.
14
+
15
+ Start here: [SETUP.md](./SETUP.md). The rules themselves: [docs/DESIGN.md](./docs/DESIGN.md).
16
+
17
+ Bringing an existing repo onto this: [docs/MIGRATION.md](./docs/MIGRATION.md)
18
+ (includes a ready-to-paste agent prompt). Ongoing upkeep once a project is on
19
+ it: [docs/MAINTENANCE.md](./docs/MAINTENANCE.md).
20
+
21
+ ## Preset
22
+
23
+ Design system preset code: `b0`
24
+
25
+ This is the one manual step in the whole kit — the code is generated
26
+ interactively and can't be scripted. Go to `ui.shadcn.com/create` and pick:
27
+ **Base UI** primitives, **neutral** base color, **new-york** style, default
28
+ radius (0.5rem), **lucide-react** icons. That reproduces the modern shadcn
29
+ default already baked into `src/styles/theme.css` in this repo, so picking it
30
+ now doesn't change anything for projects that predate the preset — it just
31
+ gives you the short code to hand to new projects and agents. Change any of
32
+ those choices later; re-running `init --preset` on an existing app is cheap
33
+ (see SETUP.md Part 3).
34
+
35
+ ```sh
36
+ pnpm dlx shadcn@latest init --preset <code>
37
+ ```
38
+
39
+ ## Install into a project
40
+
41
+ ```sh
42
+ claude plugin marketplace add MartinCa/frontend-kit
43
+ claude plugin install frontend-conventions@martinca
44
+
45
+ pnpm dlx shadcn@latest add MartinCa/frontend-kit/conventions
46
+ pnpm dlx shadcn@latest add MartinCa/frontend-kit/api-client
47
+ pnpm dlx shadcn@latest add MartinCa/frontend-kit/query-setup
48
+ pnpm dlx shadcn@latest add MartinCa/frontend-kit/theme
49
+ pnpm dlx shadcn@latest add MartinCa/frontend-kit/theme-provider
50
+ pnpm dlx shadcn@latest add MartinCa/frontend-kit/agent-skill
51
+ ```
52
+
53
+ `theme-provider` needs wiring, not just installing — wrap the app in
54
+ `<ThemeProvider>` (SETUP.md Part 6) or dark mode never activates and nothing
55
+ errors to say why. `agent-skill` vendors the conventions skill into
56
+ `.claude/skills/` so Claude Code on web and mobile sees it; the marketplace
57
+ install above only covers the local terminal. Full walkthrough in
58
+ [SETUP.md](./SETUP.md).
59
+
60
+ ## Changing a convention
61
+
62
+ Change it here, not in a project. Bump the package version for lint changes,
63
+ tag, and let Renovate deliver it. For `DESIGN.md` and shared code, commit and
64
+ reinstall in projects with `--overwrite` next time you touch them.
@@ -0,0 +1,159 @@
1
+ // Shared ESLint flat config.
2
+ //
3
+ // This file is the machine-enforceable half of DESIGN.md. Rules that can be
4
+ // linted live here; rules that need judgement live in the prose doc.
5
+ //
6
+ // Usage in a project's eslint.config.js:
7
+ //
8
+ // import config from "@martinrun/frontend-config/eslint";
9
+ // export default config();
10
+ //
11
+ // To relax a rule for one project, spread and override:
12
+ //
13
+ // export default [...config(), { rules: { "no-restricted-syntax": "off" } }];
14
+
15
+ import js from "@eslint/js";
16
+ import globals from "globals";
17
+ import tseslint from "typescript-eslint";
18
+ import reactHooks from "eslint-plugin-react-hooks";
19
+ import reactRefresh from "eslint-plugin-react-refresh";
20
+ import prettier from "eslint-config-prettier";
21
+
22
+ /**
23
+ * @param {object} [options]
24
+ * @param {string[]} [options.ignores] Extra ignore globs.
25
+ * @returns {import("eslint").Linter.Config[]}
26
+ */
27
+ export default function config({ ignores = [] } = {}) {
28
+ return tseslint.config(
29
+ { ignores: ["dist", "build", "coverage", "**/*.gen.ts", ...ignores] },
30
+
31
+ js.configs.recommended,
32
+ ...tseslint.configs.recommendedTypeChecked,
33
+
34
+ {
35
+ files: ["**/*.{ts,tsx}"],
36
+ languageOptions: {
37
+ ecmaVersion: 2022,
38
+ globals: globals.browser,
39
+ parserOptions: {
40
+ projectService: true,
41
+ tsconfigRootDir: process.cwd(),
42
+ },
43
+ },
44
+ plugins: {
45
+ "react-hooks": reactHooks,
46
+ "react-refresh": reactRefresh,
47
+ },
48
+ rules: {
49
+ ...reactHooks.configs.recommended.rules,
50
+ "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
51
+
52
+ // --- DESIGN.md section 1: TypeScript is strict, and stays strict ---
53
+ "@typescript-eslint/no-explicit-any": "error",
54
+ "@typescript-eslint/no-unsafe-assignment": "error",
55
+ "@typescript-eslint/no-unsafe-member-access": "error",
56
+ "@typescript-eslint/consistent-type-imports": [
57
+ "error",
58
+ { fixStyle: "inline-type-imports" },
59
+ ],
60
+ "@typescript-eslint/no-floating-promises": "error",
61
+ "@typescript-eslint/no-misused-promises": "error",
62
+
63
+ // --- DESIGN.md section 4: structure ---
64
+ "no-restricted-imports": [
65
+ "error",
66
+ {
67
+ patterns: [
68
+ {
69
+ group: ["../../*"],
70
+ message:
71
+ "Use the @/ alias instead of walking up more than one level. See DESIGN.md section 4.",
72
+ },
73
+ {
74
+ group: ["@radix-ui/*", "@base-ui-components/*"],
75
+ message:
76
+ "Import primitives from @/components/ui/* instead. Direct primitive imports bypass the design system. See DESIGN.md section 3.",
77
+ },
78
+ {
79
+ group: ["moment", "dayjs"],
80
+ message: "Use date-fns. See DESIGN.md section 1.",
81
+ },
82
+ ],
83
+ paths: [
84
+ {
85
+ name: "react",
86
+ importNames: ["default"],
87
+ message:
88
+ "Import named exports (useState, type ReactNode) rather than the React default export.",
89
+ },
90
+ ],
91
+ },
92
+ ],
93
+
94
+ // --- DESIGN.md section 2: state layering ---
95
+ // Server state belongs in TanStack Query, never in a Zustand store.
96
+ "no-restricted-syntax": [
97
+ "error",
98
+ {
99
+ // Both call shapes: create<S>((set) => ...) and the curried form
100
+ // create<S>()((set) => ...) that the Zustand TypeScript docs use.
101
+ // In the curried form the initializer is an argument of the *outer*
102
+ // call, so `callee.name` is undefined there and only
103
+ // `callee.callee.name` matches.
104
+ selector:
105
+ "CallExpression[callee.name='create'] CallExpression[callee.name='fetch'], CallExpression[callee.callee.name='create'] CallExpression[callee.name='fetch']",
106
+ message:
107
+ "Do not fetch inside a Zustand store. Server state belongs in TanStack Query. See DESIGN.md section 2.",
108
+ },
109
+ {
110
+ selector: "JSXAttribute[name.name='style']",
111
+ message:
112
+ "Use Tailwind utilities and theme tokens instead of inline styles. See DESIGN.md section 5.",
113
+ },
114
+ ],
115
+
116
+ // --- DESIGN.md section 6: quality floor ---
117
+ eqeqeq: ["error", "always", { null: "ignore" }],
118
+ "no-console": ["warn", { allow: ["warn", "error"] }],
119
+ },
120
+ },
121
+
122
+ // Vendored shadcn components — and the kit's own vendored components,
123
+ // like theme-provider.tsx exporting both ThemeProvider and useTheme —
124
+ // are not ours to police.
125
+ {
126
+ files: ["src/components/ui/**", "src/components/theme-provider.tsx"],
127
+ rules: {
128
+ "no-restricted-imports": "off",
129
+ "no-restricted-syntax": "off",
130
+ "react-refresh/only-export-components": "off",
131
+ "@typescript-eslint/no-unsafe-assignment": "off",
132
+ "@typescript-eslint/no-unsafe-member-access": "off",
133
+ },
134
+ },
135
+
136
+ // Generated API types are vendored too.
137
+ {
138
+ files: ["src/lib/api-types.ts"],
139
+ rules: { "@typescript-eslint/no-explicit-any": "off" },
140
+ },
141
+
142
+ // Config files run in Node and are not type-checked against the app project.
143
+ //
144
+ // disableTypeChecked carries its own `languageOptions` (parserOptions that
145
+ // switch the type-aware parser off), so it has to be spread *before* ours —
146
+ // spreading it after replaces the whole key and silently drops the Node
147
+ // globals, which shows up as no-undef on `process` in a .js config file.
148
+ {
149
+ files: ["*.config.{js,ts}"],
150
+ ...tseslint.configs.disableTypeChecked,
151
+ languageOptions: {
152
+ ...tseslint.configs.disableTypeChecked.languageOptions,
153
+ globals: globals.node,
154
+ },
155
+ },
156
+
157
+ prettier,
158
+ );
159
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@martinrun/frontend-config",
3
+ "version": "0.1.3",
4
+ "description": "Shared frontend conventions: ESLint, TypeScript and Prettier config for React + shadcn/ui projects.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MartinCa/frontend-kit.git"
10
+ },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "test": "node --test \"test/**/*.test.mjs\"",
16
+ "format": "prettier --write .",
17
+ "format:check": "prettier --check ."
18
+ },
19
+ "exports": {
20
+ "./eslint": "./eslint.config.js",
21
+ "./prettier": "./prettier.config.js",
22
+ "./tsconfig": "./tsconfig.base.json"
23
+ },
24
+ "files": [
25
+ "eslint.config.js",
26
+ "prettier.config.js",
27
+ "tsconfig.base.json",
28
+ "LICENSE"
29
+ ],
30
+ "peerDependencies": {
31
+ "eslint": ">=9",
32
+ "prettier": ">=3",
33
+ "prettier-plugin-tailwindcss": ">=0.6",
34
+ "typescript": ">=5.5"
35
+ },
36
+ "dependencies": {
37
+ "@eslint/js": "^10.0.0",
38
+ "eslint-config-prettier": "^10.0.0",
39
+ "eslint-plugin-react-hooks": "^7.0.0",
40
+ "eslint-plugin-react-refresh": "^0.5.0",
41
+ "globals": "^17.0.0",
42
+ "typescript-eslint": "^8.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "eslint": "^10.9.1",
46
+ "prettier": "^3.9.6",
47
+ "prettier-plugin-tailwindcss": "^0.8.1",
48
+ "typescript": "^6.0.3"
49
+ }
50
+ }
@@ -0,0 +1,9 @@
1
+ /** @type {import("prettier").Config} */
2
+ export default {
3
+ semi: true,
4
+ singleQuote: false,
5
+ trailingComma: "all",
6
+ printWidth: 100,
7
+ tabWidth: 2,
8
+ plugins: ["prettier-plugin-tailwindcss"],
9
+ };
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compilerOptions": {
4
+ "target": "ES2022",
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "jsx": "react-jsx",
9
+ "useDefineForClassFields": true,
10
+
11
+ "strict": true,
12
+ "noUncheckedIndexedAccess": true,
13
+ "noImplicitOverride": true,
14
+ "noFallthroughCasesInSwitch": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+
18
+ // The one relaxed strictness flag, and deliberately so: with it on, every
19
+ // optional prop spread into a shadcn component or a react-hook-form field
20
+ // needs `| undefined` written out, which is friction on vendored code we do
21
+ // not control. Turn it on per project if the project's own code benefits.
22
+ "exactOptionalPropertyTypes": false,
23
+
24
+ "verbatimModuleSyntax": true,
25
+ "isolatedModules": true,
26
+ "esModuleInterop": true,
27
+ "resolveJsonModule": true,
28
+ "skipLibCheck": true,
29
+ "noEmit": true
30
+ }
31
+ }