fg-devkit 1.23.1 → 1.24.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/README.md CHANGED
@@ -89,12 +89,26 @@ To set up Lefthook git hooks:
89
89
  pnpm exec fgd setup-lefthook
90
90
  ```
91
91
 
92
+ To use the shared AI rules from [feelgood/fg-ai-rules](https://github.com/feelgood/fg-ai-rules) in Claude Code:
93
+
94
+ ```bash
95
+ pnpm exec fgd setup-ai-rules
96
+ ```
97
+
98
+ It enables the `fg-core` and `fg-frontend` plugins in `.claude/settings.json`, adds the `fg` marketplace
99
+ to Claude Code if it is missing, and installs the plugins for the repo. Claude Code doesn't install them from
100
+ the settings file on its own, so every developer runs it once per repo; commit `.claude/settings.json`
101
+ the first time. Needs Claude Code and SSH access to `feelgood/fg-ai-rules`.
102
+
92
103
  Run full consumer setup in one command:
93
104
 
94
105
  ```bash
95
106
  pnpm exec fgd full-consumer-setup
96
107
  ```
97
108
 
109
+ It syncs the consumer scripts, Lefthook, configs and dependency policy, runs `pnpm install`, and finishes with
110
+ `setup-ai-rules`. Without Claude Code that last step only prints a warning.
111
+
98
112
  ## Dependency policy update workflow
99
113
 
100
114
  Check outdated policy versions:
@@ -18,7 +18,7 @@
18
18
  "@lexical/link": "0.52.0",
19
19
  "@piwikpro/vue-piwik-pro": "2.0.0",
20
20
  "@sentry/vue": "11.7.0",
21
- "@tanstack/vue-query": "5.104.2",
21
+ "@tanstack/vue-query": "5.104.3",
22
22
  "@types/leaflet": "1.9.22",
23
23
  "@unhead/vue": "2.0.5",
24
24
  "@vue-leaflet/vue-leaflet": "0.10.1",
@@ -38,7 +38,7 @@
38
38
  "lodash-es": "4.18.1",
39
39
  "markdown-it": "15.0.2",
40
40
  "pinia": "4.1.0",
41
- "playwright": "1.64.0",
41
+ "playwright": "1.64.1",
42
42
  "quill": "2.0.3",
43
43
  "randexp": "0.5.3",
44
44
  "register-service-worker": "1.7.2",
@@ -56,14 +56,14 @@
56
56
  "devDependencies": {
57
57
  "@chromatic-com/storybook": "5.5.1",
58
58
  "@eslint/js": "9.39.1",
59
- "@intlify/unplugin-vue-i18n": "11.2.5",
60
- "@playwright/test": "1.64.0",
59
+ "@intlify/unplugin-vue-i18n": "11.2.6",
60
+ "@playwright/test": "1.64.1",
61
61
  "@storybook/addon-a11y": "10.6.2",
62
62
  "@storybook/addon-docs": "10.6.2",
63
63
  "@storybook/addon-vitest": "10.6.2",
64
64
  "@storybook/vue3": "10.6.2",
65
65
  "@storybook/vue3-vite": "10.6.2",
66
- "@tanstack/vue-query-devtools": "6.3.2",
66
+ "@tanstack/vue-query-devtools": "6.3.3",
67
67
  "@tsconfig/node22": "22.0.6",
68
68
  "@types/lodash-es": "4.17.12",
69
69
  "@types/node": "22.16.5",
@@ -92,7 +92,7 @@
92
92
  "@tailwindcss/vite": "4.3.3",
93
93
  "@vitejs/plugin-vue": "6.0.10",
94
94
  "axios": "1.20.0",
95
- "dotenv": "18.0.7",
95
+ "dotenv": "18.0.8",
96
96
  "qrcode.vue": "3.11.0",
97
97
  "tailwindcss": "4.3.3",
98
98
  "vite": "8.3.4",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fg-devkit",
3
- "version": "1.23.1",
3
+ "version": "1.24.0",
4
4
  "description": "Shared configs, scripts, and DX tooling for Vue / TypeScript frontend repos",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -26,8 +26,7 @@
26
26
  "./release-notes/format": "./release-notes/format.ts",
27
27
  "./release-notes/types": "./release-notes/types.ts",
28
28
  "./configs/cliff.toml": "./configs/cliff.toml",
29
- "./configs/cliff.user.toml": "./configs/cliff.user.toml",
30
- "./cursor-rules/core-coding-standards.mdc": "./.cursor/rules/core-coding-standards.mdc"
29
+ "./configs/cliff.user.toml": "./configs/cliff.user.toml"
31
30
  },
32
31
  "files": [
33
32
  "configs",
@@ -36,8 +35,7 @@
36
35
  "!**/node_modules",
37
36
  "!**/node_modules/**",
38
37
  "scripts",
39
- "tsconfig.json",
40
- ".cursor/rules/core-coding-standards.mdc"
38
+ "tsconfig.json"
41
39
  ],
42
40
  "engines": {
43
41
  "node": ">=22.13.0"
@@ -16,11 +16,11 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@tailwindcss/vite": "4.3.3",
19
- "@tanstack/vue-query": "5.104.2",
19
+ "@tanstack/vue-query": "5.104.3",
20
20
  "axios": "1.20.0",
21
21
  "content-disposition-header": "0.6.0",
22
22
  "date-fns": "4.4.0",
23
- "dotenv": "18.0.7",
23
+ "dotenv": "18.0.8",
24
24
  "fds-vue-core": "8.11.0",
25
25
  "pinia": "4.1.0",
26
26
  "qrcode.vue": "3.11.0",
@@ -29,10 +29,10 @@
29
29
  "vue-router": "5.4.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@intlify/unplugin-vue-i18n": "11.2.5",
33
- "@tanstack/vue-query-devtools": "6.3.2",
32
+ "@intlify/unplugin-vue-i18n": "11.2.6",
33
+ "@tanstack/vue-query-devtools": "6.3.3",
34
34
  "@types/node": "22.16.5",
35
- "fg-devkit": "1.23.1",
35
+ "fg-devkit": "1.24.0",
36
36
  "@vitejs/plugin-vue": "6.0.10",
37
37
  "tailwindcss": "4.3.3",
38
38
  "tsx": "4.23.15",
@@ -23,7 +23,6 @@
23
23
  * pnpm exec fg-devkit sync-tooling-configs (tooling sync only; overwrites config files)
24
24
  * pnpm exec fg-devkit sync-dependency-check-suppressions-commit [--all]
25
25
  * pnpm exec fg-devkit sync-vscode-settings
26
- * pnpm exec fg-devkit sync-core-coding-standards
27
26
  * pnpm exec fg-devkit sync-consumer-scripts
28
27
  * pnpm exec fg-devkit setup-lefthook
29
28
  * pnpm exec fg-devkit full-consumer-setup
@@ -165,6 +164,7 @@ function runSetupLefthook() {
165
164
  * - Step 3: sync project configs
166
165
  * - Step 2: sync dependency policy
167
166
  * - Then run pnpm install
167
+ * - Set up the fg-ai-rules Claude Code plugins (a failure only warns, e.g. without Claude Code)
168
168
  */
169
169
  function runFullConsumerSetup() {
170
170
  console.log('[fg-devkit] Running full consumer sync and setup')
@@ -184,7 +184,13 @@ function runFullConsumerSetup() {
184
184
  const step2 = runNode('sync-dependency-policy.mjs')
185
185
  if (step2 !== 0) return step2
186
186
 
187
- return runPnpmInstall()
187
+ const installStep = runPnpmInstall()
188
+ if (installStep !== 0) return installStep
189
+
190
+ if (runNode('setup-ai-rules.mjs') !== 0) {
191
+ console.warn('[fg-devkit] AI rules were not set up. Run "fgd setup-ai-rules" when Claude Code is available.')
192
+ }
193
+ return 0
188
194
  }
189
195
 
190
196
  /** Maintainer flow: run full repo vulnerability audit in fg-devkit root. */
@@ -471,7 +477,6 @@ function runSyncConfigTarget(target) {
471
477
  if (target === 'prettier') return syncFileFromTemplate('configs/prettier.sync-stub.js', 'prettier.config.js')
472
478
  if (target === 'tsconfig') return syncFileFromTemplate('configs/tsconfig.app.sync.json', 'tsconfig.json')
473
479
  if (target === 'dependency-check') return runNode('sync-dependency-check-suppressions-commit.mjs')
474
- if (target === 'cursor') return runNode('sync-core-coding-standards.mjs')
475
480
  if (target === 'vue-inspector') return runNode('sync-vite-plugin-vue-inspector.mjs')
476
481
  if (target === 'all') return runNode('sync-project-configs.mjs')
477
482
  console.error(`[fg-devkit] Unknown config sync target: ${target}`)
@@ -504,9 +509,9 @@ const DIRECT_COMMANDS = {
504
509
  'sync-tooling-configs': 'sync-tooling-configs.mjs',
505
510
  'sync-dependency-check-suppressions-commit': 'sync-dependency-check-suppressions-commit.mjs',
506
511
  'sync-vscode-settings': 'sync-vscode-settings.mjs',
507
- 'sync-core-coding-standards': 'sync-core-coding-standards.mjs',
508
512
  'sync-vite-plugin-vue-inspector': 'sync-vite-plugin-vue-inspector.mjs',
509
513
  'sync-consumer-scripts': 'sync-consumer-scripts.mjs',
514
+ 'setup-ai-rules': 'setup-ai-rules.mjs',
510
515
  'sync-release-notes-config': 'sync-release-notes-config.mjs',
511
516
  'generate-release-notes': 'generate-release-notes.mjs',
512
517
  'verify-release-notes': 'verify-release-notes.mjs',
@@ -569,11 +574,11 @@ Commands:
569
574
  sync-dependency-check-suppressions-commit [--all]
570
575
  Write the dependency-check suppressions that match pnpm audit findings and auto-commit (--all: copy every entry)
571
576
  sync-vscode-settings Copy shared VS Code settings into .vscode/settings.json
572
- sync-core-coding-standards Copy .cursor core coding standards with consumer package name
573
577
  sync-vite-plugin-vue-inspector Add/pin vite-plugin-vue-inspector, wire vite.config, install, commit
574
578
  sync-consumer-scripts Enforce standard fgd scripts in consumer package.json
575
579
  setup-lefthook Write lefthook.yml and install the git hooks
576
- full-consumer-setup Run: scripts -> lefthook -> step 3 -> step 2 -> pnpm install
580
+ setup-ai-rules Enable the fg-ai-rules Claude Code plugins in this repo and install them
581
+ full-consumer-setup Run: scripts -> lefthook -> step 3 -> step 2 -> pnpm install -> AI rules
577
582
  create-release [release|candidate] [patch|minor|major] [--skip-release-notes]
578
583
  Stable release or candidate (X.Y.Z-rc.N); omit = interactive menu
579
584
  (alias: createRelease)
@@ -1061,7 +1066,6 @@ async function runInteractiveMenu() {
1061
1066
  value: 'dependency-check',
1062
1067
  description: 'Write the suppressions that match pnpm audit findings',
1063
1068
  },
1064
- { name: 'Cursor rules', value: 'cursor', description: 'Write .cursor/rules/core-coding-standards.mdc' },
1065
1069
  {
1066
1070
  name: autoCommit('Vue inspector'),
1067
1071
  value: 'vue-inspector',
@@ -1086,10 +1090,15 @@ async function runInteractiveMenu() {
1086
1090
  value: 'lefthook',
1087
1091
  description: 'Write lefthook.yml and install the git hooks',
1088
1092
  },
1093
+ {
1094
+ name: 'AI rules',
1095
+ value: 'ai-rules',
1096
+ description: 'Enable the fg-ai-rules plugins in .claude/settings.json and install them for Claude Code',
1097
+ },
1089
1098
  {
1090
1099
  name: 'Full setup',
1091
1100
  value: 'full-consumer-setup',
1092
- description: 'Sync scripts, Lefthook, configs and dependency policy, then pnpm install',
1101
+ description: 'Sync scripts, Lefthook, configs and dependency policy, pnpm install, then set up AI rules',
1093
1102
  },
1094
1103
  { name: 'Cancel', value: 'cancel' },
1095
1104
  ],
@@ -1097,6 +1106,7 @@ async function runInteractiveMenu() {
1097
1106
  loop: false,
1098
1107
  })
1099
1108
  if (choice === 'cancel') continue
1109
+ if (choice === 'ai-rules') return runSelectedAction(() => runNode('setup-ai-rules.mjs'))
1100
1110
  return runSelectedAction(() => (choice === 'lefthook' ? runSetupLefthook() : runFullConsumerSetup()))
1101
1111
  }
1102
1112
 
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Set up the shared AI rules from feelgood/fg-ai-rules for Claude Code in the current repo:
4
+ *
5
+ * 1. Add the `fg` marketplace and enable fg-core and fg-frontend in .claude/settings.json,
6
+ * keeping every other setting.
7
+ * 2. Add the marketplace to Claude Code on this machine if it is missing.
8
+ * 3. Install the plugins for this repo (`claude plugin install … --scope project`). Claude Code
9
+ * does not install them from the settings file on its own, and without the install they don't
10
+ * load at session start.
11
+ *
12
+ * Safe to run again: it only changes what is missing. Commit .claude/settings.json afterwards so
13
+ * teammates get the same plugins; each of them still runs this once.
14
+ *
15
+ * Usage:
16
+ * pnpm exec fg-devkit setup-ai-rules
17
+ */
18
+
19
+ import { spawnSync } from 'node:child_process'
20
+ import fs from 'node:fs'
21
+ import path from 'node:path'
22
+
23
+ const TAG = '[setup-ai-rules]'
24
+ const MARKETPLACE = 'fg'
25
+ const MARKETPLACE_REPO = 'feelgood/fg-ai-rules'
26
+ const PLUGINS = ['fg-core', 'fg-frontend']
27
+
28
+ const projectRoot = fs.realpathSync(process.cwd())
29
+ const settingsPath = path.join(projectRoot, '.claude', 'settings.json')
30
+
31
+ function claude(args, { capture = false } = {}) {
32
+ const result = spawnSync('claude', args, {
33
+ cwd: projectRoot,
34
+ encoding: 'utf8',
35
+ stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'inherit',
36
+ })
37
+ if (result.error) return { status: 1, stdout: '', missing: result.error.code === 'ENOENT' }
38
+ return { status: result.status ?? 1, stdout: result.stdout ?? '' }
39
+ }
40
+
41
+ function claudeJson(args) {
42
+ const result = claude([...args, '--json'], { capture: true })
43
+ if (result.status !== 0) return null
44
+ try {
45
+ return JSON.parse(result.stdout)
46
+ } catch {
47
+ return null
48
+ }
49
+ }
50
+
51
+ /** Add the marketplace and enabled plugins to .claude/settings.json. Returns true when it changed. */
52
+ function updateSettings() {
53
+ let settings = {}
54
+ if (fs.existsSync(settingsPath)) {
55
+ try {
56
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'))
57
+ } catch (err) {
58
+ console.error(`${TAG} Could not parse ${settingsPath}: ${err.message}`)
59
+ process.exit(1)
60
+ }
61
+ }
62
+ const before = JSON.stringify(settings)
63
+ settings.extraKnownMarketplaces = {
64
+ ...settings.extraKnownMarketplaces,
65
+ [MARKETPLACE]: { source: { source: 'github', repo: MARKETPLACE_REPO }, autoUpdate: true },
66
+ }
67
+ settings.enabledPlugins = {
68
+ ...settings.enabledPlugins,
69
+ ...Object.fromEntries(PLUGINS.map((plugin) => [`${plugin}@${MARKETPLACE}`, true])),
70
+ }
71
+ if (JSON.stringify(settings) === before) return false
72
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true })
73
+ fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`)
74
+ return true
75
+ }
76
+
77
+ function printManualSteps() {
78
+ console.log(`${TAG} Run these from the repo root, then restart Claude Code:`)
79
+ console.log(` claude plugin marketplace add ${MARKETPLACE_REPO}`)
80
+ for (const plugin of PLUGINS) console.log(` claude plugin install ${plugin}@${MARKETPLACE} --scope project`)
81
+ }
82
+
83
+ function main() {
84
+ if (!fs.existsSync(path.join(projectRoot, 'package.json'))) {
85
+ console.error(`${TAG} Run from the repo root (no package.json in ${projectRoot}).`)
86
+ return 1
87
+ }
88
+
89
+ const settingsChanged = updateSettings()
90
+ console.log(`${TAG} ${settingsChanged ? 'Updated' : 'Already up to date:'} .claude/settings.json`)
91
+
92
+ const marketplaces = claudeJson(['plugin', 'marketplace', 'list'])
93
+ if (marketplaces === null) {
94
+ const missing = claude(['--version'], { capture: true }).missing
95
+ console.error(`${TAG} ${missing ? 'Claude Code (claude) is not installed.' : 'Could not read Claude Code plugins.'}`)
96
+ printManualSteps()
97
+ return 1
98
+ }
99
+
100
+ if (!marketplaces.some((marketplace) => marketplace.name === MARKETPLACE)) {
101
+ console.log(`${TAG} Adding the ${MARKETPLACE} marketplace (${MARKETPLACE_REPO})…`)
102
+ if (claude(['plugin', 'marketplace', 'add', MARKETPLACE_REPO]).status !== 0) {
103
+ console.error(`${TAG} Could not add the marketplace. Check that you can clone ${MARKETPLACE_REPO} over SSH.`)
104
+ return 1
105
+ }
106
+ }
107
+
108
+ const installed = claudeJson(['plugin', 'list']) ?? []
109
+ const isInstalledHere = (plugin) =>
110
+ installed.some(
111
+ (entry) =>
112
+ entry.id === `${plugin}@${MARKETPLACE}` &&
113
+ (entry.scope === 'user' || (entry.projectPath && fs.realpathSync(entry.projectPath) === projectRoot)),
114
+ )
115
+
116
+ let failed = false
117
+ for (const plugin of PLUGINS) {
118
+ if (isInstalledHere(plugin)) {
119
+ console.log(`${TAG} ${plugin} is already installed for this repo.`)
120
+ continue
121
+ }
122
+ console.log(`${TAG} Installing ${plugin}…`)
123
+ if (claude(['plugin', 'install', `${plugin}@${MARKETPLACE}`, '--scope', 'project']).status !== 0) failed = true
124
+ }
125
+ if (failed) {
126
+ printManualSteps()
127
+ return 1
128
+ }
129
+
130
+ console.log(`${TAG} Done. Restart Claude Code to load the rules.`)
131
+ if (settingsChanged) console.log(`${TAG} Commit .claude/settings.json so teammates get the same plugins.`)
132
+ return 0
133
+ }
134
+
135
+ process.exit(main())
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  import fs from 'node:fs'
16
- import path from 'node:path'
17
16
 
18
17
  import {
19
18
  collectNodeToolchainPolicyViolations,
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import fs from 'node:fs'
10
- import path from 'node:path'
11
10
 
12
11
  import {
13
12
  collectToolchainPolicyViolations,
@@ -1,291 +0,0 @@
1
- ---
2
- description: Core coding standards for Vue / fds-vue-core frontend projects
3
- alwaysApply: true
4
- ---
5
-
6
- # Core Coding Standards
7
-
8
- ## Keep changes focused
9
-
10
- - Make the smallest safe change that solves the request.
11
- - Avoid unrelated refactors in the same task.
12
- - Preserve existing behavior unless a behavior change is requested.
13
-
14
- ## Code comments
15
-
16
- - Do not add comments in code unless the user explicitly asks for them.
17
- - Prefer self-explanatory naming and structure over inline comments.
18
- - When updating existing code, do not add new comments or expand existing ones unless requested.
19
- - Exception: update comments only when they are required to keep an existing comment accurate after a behavior change in the same task.
20
-
21
- ## Communication efficiency
22
-
23
- - Do not waste tokens: keep responses concise, high-signal, and scoped to the request.
24
- - Prefer short summaries over long explanations unless deeper detail is explicitly requested.
25
-
26
- ## Evidence-first implementation
27
-
28
- - Never guess or hallucinate APIs, component props, or framework behavior.
29
- - Verify implementation details against authoritative sources first (official docs, project code, and library types/source).
30
- - If documentation is missing or ambiguous, state uncertainty explicitly and choose the safest validated approach.
31
-
32
- ## Match project conventions
33
-
34
- - Follow established naming, file structure, and component patterns.
35
- - Reuse existing utilities and query/composable patterns before adding new ones.
36
- - Keep TypeScript types explicit at API boundaries.
37
-
38
- ## Strict TypeScript
39
-
40
- - Keep TypeScript and Vue template typing strict (`strict: true`, `strictTemplates: true`).
41
- - Do not introduce `any`; use `unknown` for unsafe input and narrow before use.
42
- - Type API boundaries explicitly (request payloads, response DTOs, mutation variables, callback contracts).
43
- - Prefer named reusable types under `src/types/<domain>/` for shared domain shapes; re-export from `src/types/index.ts`. Do not keep shared interfaces in views, stores, or query files.
44
- - Avoid non-null assertions unless guarded by `enabled` checks or equivalent runtime guarantees.
45
-
46
- ## API boundaries
47
-
48
- - Components and views should not call low-level HTTP/API clients directly for feature data fetching.
49
- - Centralize HTTP and endpoint wiring in a dedicated API layer (for example `src/api/endpoints/*` or the convention used in the repo).
50
- - Consume server state through query/mutation hooks (for example `src/queries/*` or equivalent), not ad-hoc fetches in UI code.
51
-
52
- ## Query strings
53
-
54
- - Build query strings with `URLSearchParams` (or a shared helper that wraps it).
55
- - Do not concatenate `key=value` fragments with `&`.
56
- - Let `URLSearchParams` encode values. Do not interpolate untrusted or user-entered values into URLs.
57
- - `toString()` has no leading `?`. Prefix it when appending to a path, and skip empty values instead of emitting empty keys.
58
- - For list views, derive the query string in the view from route filter state (`requestQueryParams` computed). The API layer should accept the query string, not reconstruct filters.
59
-
60
- ## State-management boundaries
61
-
62
- - Use TanStack Query for server state and caching. Do not add Vuex for new work.
63
- - Use Pinia only for client/app state that is not owned by the server (for example `src/pinia/*`).
64
- - Do not duplicate the same state across the query cache and Pinia unless the project explicitly documents that pattern.
65
- - When a project is migrating Vuex → Query/Pinia, follow that repo’s local migration rules (if present) instead of expanding Vuex.
66
-
67
- ## TanStack Query server-state conventions
68
-
69
- - Use TanStack Query for server state; avoid duplicating server data in ad-hoc local state.
70
- - Keep API calls in the API layer and expose query/mutation hooks from a dedicated hooks folder.
71
- - Build query keys as stable arrays of serializable primitives; when using refs, key by `.value` in a computed key.
72
- - Prefer shared query key constants/factories to keep invalidation and cache updates consistent.
73
- - Add `enabled` guards for required IDs/paths to prevent invalid requests.
74
- - Use `placeholderData: keepPreviousData` for paginated/filter-driven views to avoid UI flicker.
75
- - Set `staleTime` intentionally per resource; keep `refetchOnWindowFocus` off unless fresh-on-focus behavior is required.
76
- - On mutation success, update cache intentionally (`setQueryData`) and/or invalidate the narrowest matching keys.
77
- - Respect shared query client retry behavior and only override retries when endpoint-specific behavior is needed.
78
-
79
- ### Query loading UI
80
-
81
- TanStack Query v5 flags (do not treat them as interchangeable):
82
-
83
- | Flag | Meaning |
84
- | --- | --- |
85
- | `isPending` | No cached data yet (`status === 'pending'`) |
86
- | `isFetching` | Any in-flight request, including background refetch |
87
- | `isLoading` | First fetch in flight (`isPending && isFetching`) |
88
-
89
- Rules:
90
-
91
- 1. **Content-blocking / full-page spinner** — use `isLoading` (or combine several `isLoading*` flags). Prefer `isLoading` over bare `isPending` when the query can be `enabled: false` (lazy/disabled queries stay `isPending` without fetching).
92
- 2. **Do not use bare `isFetching` for blocking UI** — it re-shows the full loader on every background refetch. Use `isFetching` only for subtle “refreshing” indicators while keeping content visible.
93
- 3. **Paginated / `keepPreviousData` lists** — show a hard loader only when there is nothing useful to render:
94
-
95
- ```ts
96
- const isLoading = computed(() => isPending.value || (isFetching.value && !data.value))
97
- ```
98
-
99
- 4. **Multiple queries for one screen** — combine with OR into one (or purpose-prefixed) computed:
100
-
101
- ```ts
102
- const isLoading = computed(() => isLoadingDetails.value || isLoadingList.value)
103
- ```
104
-
105
- 5. **No passthrough aliases** — do not wrap a single flag (`const loading = computed(() => isFetching.value)`). Destructure and rename instead:
106
-
107
- ```ts
108
- const { data, isLoading, error } = useGetDetails(id)
109
- // template: v-show="isLoading"
110
- ```
111
-
112
- 6. **Naming** — local query-derived booleans use the `is*` prefix: `isLoading`, `isLoadingUnits`, `isLoadingHistory`. Keep FDS/component prop names as-is (`:loading="isLoading"`). Mutations: rename `isPending` to intent (`isSaving`, `isSubmitting`, `isUploading`).
113
-
114
- - TanStack Query boolean flags (`isPending`, `isSuccess`, `isError`) are typed `Ref<false> | Ref<true>`. `vue-tsc` unwraps that union in templates to `boolean`; the Vue language plugin may not.
115
- - Narrow in script (`const isPending = mutation.isPending as Ref<boolean>`), then bind without `.value` (`:loading="isPending"`). Do not use `.value` in templates (fails `vue-tsc`) and do not add a wrapping `computed` only to satisfy the editor.
116
-
117
- ## Mutation cache behavior
118
-
119
- - Every mutation should define post-success cache behavior.
120
- - Use `invalidateQueries` for stale collections and `setQueryData` for direct entity updates.
121
- - Invalidate the narrowest query keys that match changed resources.
122
-
123
- ## Vue Router conventions
124
-
125
- - Organize route definitions in modules (for example `src/router/modules/*`) composed from a single router entry (for example `src/router/index.ts`), matching the repo layout.
126
- - Every user-facing route should define `meta.title`; provide breadcrumb metadata for detail/flow routes when needed.
127
- - Use shared route guards for auth/anonymous access control instead of duplicating checks in views.
128
- - Prefer `props: true` and typed route params in components instead of reading raw params everywhere.
129
- - Prefer named-route navigation for internal flows (`{ name, params, query }`) and reserve raw path pushes for simple/static redirects.
130
- - Preserve existing query params intentionally when updating filters, pagination, or tab state.
131
- - Returning to a view whose filters live in the URL: call `usePreviousLocation` from `fds-vue-core` in setup. `router.push('/path')` drops that query. Browser back keeps it.
132
- - Use `hrefFor` on `router-link` and `FdsTabsItem`, and `goTo` on back buttons, only when the target is that same view. Leave other navigations unchanged.
133
- - `hrefFor(path)` reuses the previous history entry, including `_search`, `_page`, and filters, when its pathname matches. Otherwise it returns the path only.
134
- - `goTo(fallback, matchPaths?)` calls `router.back()` when the previous entry matches, so scroll is kept. Otherwise it pushes the fallback path.
135
- - Pass `matchPaths` when several paths count as the same place. Without it, only the fallback path is compared.
136
-
137
- ```ts
138
- import { usePreviousLocation } from 'fds-vue-core'
139
-
140
- const { hrefFor, goTo } = usePreviousLocation()
141
-
142
- const goBack = () => {
143
- goTo('/services/J4', ['/services/J4', '/services'])
144
- }
145
- ```
146
-
147
- ```vue
148
- <router-link :to="hrefFor(breadcrumb.path)">
149
- {{ breadcrumb.name }}
150
- </router-link>
151
- <FdsTabsItem :to="hrefFor(tab.path)" />
152
- <FdsButtonMinor :text="t('common.back')" icon="left" @click="goBack" />
153
- ```
154
-
155
- - Lazy-load route components with dynamic imports to keep initial bundles smaller.
156
- - Name organisation route params by domain meaning, not generic `id`:
157
- - top org: `:topClientNumber`
158
- - unit/sub-org: `:unitClientNumber`
159
- - Prefer `/organisations/:topClientNumber/units/:unitClientNumber` over `:id` / `:clino`.
160
- - Keep route param names aligned with component props when using `props: true`.
161
-
162
- ## Vue and UI expectations
163
-
164
- - Prefer clear, readable templates over compact but hard-to-follow expressions.
165
- - Keep components focused; extract repeated UI blocks when duplication grows.
166
- - Keep localization text in language files instead of hardcoded UI strings.
167
-
168
- ## FDS-first component usage
169
-
170
- - Use `fds-vue-core` components as the default choice for UI building blocks.
171
- - Multi-step wizards use `FdsWizard` from `fds-vue-core`. Follow the project’s wizard rule file when one exists (for example `.cursor/rules/fds-wizard.md`).
172
- - Before adding custom UI, check whether an FDS component already solves the use case.
173
- - Prefer extending composition around FDS components over re-implementing design-system behavior.
174
- - Keep visual consistency by matching FDS variants, spacing patterns, and interaction styles.
175
-
176
- ## Asset creation policy
177
-
178
- - Do not create icons, images, illustrations, or placeholder content unless explicitly requested.
179
- - Assume `fds-vue-core` provides the required visual assets and use those first.
180
- - If a required asset appears missing, clearly point out the gap instead of inventing a replacement.
181
-
182
- ## FDS typography (fds-vue-core)
183
-
184
- - For headings, use `FdsH1`, `FdsH2`, and `FdsH3` (not raw `h1/h2/h3`).
185
- - `FdsH1/FdsH2/FdsH3` use the `text` prop for content and support `size` (`sm|md|lg|xl|responsive`).
186
- - When layout spacing needs to be neutralized, reset heading spacing with `class="m-0"` (and add explicit margins as needed).
187
- - Use `FdsLead` for intro/lead paragraphs, `FdsMeta` for metadata/secondary text, and `FdsLabel` for short labels.
188
- - `FdsLead`, `FdsMeta`, and `FdsLabel` render slot content; prefer these over custom styled spans for text semantics.
189
-
190
- ## Tailwind (v4+)
191
-
192
- - Use Tailwind v4 patterns and utilities (for example `tailwindcss@4` with `@tailwindcss/vite` where applicable).
193
- - Prefer utility classes in templates; only add custom CSS when utilities cannot express the design.
194
- - Follow existing FDS token/preset styling (`fds-vue-core` tokens and preset) instead of inventing parallel design tokens.
195
- - Always use Tailwind/FDS design tokens for colors, spacing, typography, radius, and shadows.
196
- - Never invent ad-hoc color values (for example hex/rgb/hsl literals) when a tokenized utility exists.
197
- - Keep spacing and typography explicit in class names; avoid hidden styling in deep selectors.
198
- - Use `!` utility overrides sparingly and only when a component default cannot be changed safely.
199
- - For Tailwind v4 important modifiers, use postfix-important utility syntax (for example `grid-cols-1!`) instead of prefix syntax (for example `!grid-cols-1`).
200
- - Stack repeated items with parent `flex flex-col gap-*` (or `space-y-*`). Do not use `mb-* last:mb-0` or `last-of-type:mb-*` — those variants often miss the last `v-for` item (extra Vue/FDS nodes) and trip Tailwind conflict lint.
201
-
202
- ## Figma MCP implementation workflow
203
-
204
- - When using Figma MCP, implement the design as specified (layout, spacing, typography, states, and interactions).
205
- - Treat the Figma file as source of truth for UI behavior unless product/technical constraints are explicitly documented.
206
- - Primary choice for implementation is `fds-vue-core` components that best match the design intent.
207
- - Only create custom UI when no suitable FDS component exists; keep custom parts minimal and design-token based.
208
- - If any Figma detail cannot be implemented exactly, document the deviation and choose the closest FDS-aligned solution.
209
-
210
- ## i18n and content
211
-
212
- - Do not hardcode user-facing copy in templates or script logic; use `t(...)` keys.
213
- - Add or update locale entries under the project’s language folder (for example `src/lang/*.json`) when introducing new text.
214
- - Keep translation keys descriptive and grouped by screen/feature.
215
- - Avoid storing i18n keys inside data/definition arrays (e.g. `i18nKey`) and translating later via dynamic keys or large switch statements. Prefer building definitions with already-translated `label: t('...')` (often as a `computed`) so types stay strict and templates stay simple.
216
-
217
- ## Formatting and locale consistency
218
-
219
- - Prefer shared formatting utilities for date/number/currency output instead of ad-hoc inline formatting.
220
- - Use Swedish locale conventions (`sv-SE`) unless a feature explicitly requires another locale.
221
-
222
- ## Accessibility baseline
223
-
224
- - Use semantic HTML and accessible FDS components first; avoid click-only `div`/`span` patterns.
225
- - Ensure form controls have proper labels and required ARIA attributes when needed.
226
- - All interactive elements must be keyboard reachable and show visible focus states.
227
- - Provide meaningful accessible names for icon-only actions and links.
228
-
229
- ## Async UI states
230
-
231
- - For data-driven screens, always handle loading, empty, error, and success states explicitly.
232
- - Use consistent FDS-based feedback patterns for loaders, alerts, and empty-state messaging.
233
-
234
- ## Error handling UX
235
-
236
- - Do not swallow errors silently; surface actionable feedback to users.
237
- - Use consistent FDS feedback patterns for API failures (alerts/toasts/inline messages).
238
- - Keep technical error details out of user-facing copy unless explicitly required.
239
-
240
- ## Performance guardrails
241
-
242
- - Avoid unnecessary reactive churn (redundant watchers/computed chains in hot paths).
243
- - Memoize expensive derived values and keep render-time computations lightweight.
244
- - Keep route-level and heavy feature components lazy-loaded.
245
-
246
- ## Security and data handling
247
-
248
- - Never commit or expose secrets, tokens, or sensitive identifiers in code, logs, or UI.
249
- - Validate and sanitize untrusted values from route/query params before use.
250
- - Type and validate outgoing payloads to prevent accidental over-posting.
251
-
252
- ## Testing expectations
253
-
254
- - Do not add new automated tests unless the user explicitly asks for them.
255
- - Update existing tests when your change requires it (for example broken assertions, renamed APIs, or changed behavior covered by current tests).
256
- - When new tests are requested, add them close to the affected feature.
257
- - Keep tests behavior-focused and avoid brittle implementation-detail assertions.
258
- - When no new tests are requested, rely on lint, typecheck, and manual verification of critical flows where practical.
259
-
260
- ## CI/CD
261
-
262
- - Treat the repository’s CI configuration as the source of truth (`Jenkinsfile`, GitHub Actions, or equivalent).
263
- - Prefer frozen lockfile installs in CI (for example `pnpm install --frozen-lockfile --prefer-offline`) so CI matches local installs.
264
- - Keep build, lint, typecheck, and test stages deterministic; document any project-specific stages (security scans, i18n download, Docker publish) in the repo README or a dedicated rule file.
265
-
266
- ## Quality checks before finishing
267
-
268
- - Run relevant checks for touched code (lint and typecheck when practical).
269
- - Run and update existing tests when your change affects them; add new tests only when the user explicitly requests them.
270
- - Fix issues introduced by your change before handing off.
271
- - Call out anything you could not verify.
272
-
273
- ## Definition of done
274
-
275
- - Lint and type checks pass for touched code.
276
- - UI matches Figma/FDS expectations and uses tokenized styling.
277
- - i18n keys, async states, and cache behavior are handled for changed features.
278
- - Note any constraints, deviations, or unverified parts in handoff.
279
-
280
- ## Legacy/deprecation policy
281
-
282
- - For new work, follow the current preferred patterns in this document.
283
- - Do not expand usage of known legacy patterns when a supported modern pattern exists.
284
- - If migration is out of scope, isolate legacy touchpoints and avoid spreading them.
285
-
286
- ## Example: API data safety
287
-
288
- ```ts
289
- // Prefer typed fallbacks when API data may be missing.
290
- const total = response?.stats?.total ?? 0
291
- ```
@@ -1,62 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Syncs .cursor/rules/core-coding-standards.mdc into the consumer repo.
4
- * Rewrites frontmatter description to include the consumer package name.
5
- *
6
- * Usage:
7
- * node node_modules/fg-devkit/scripts/sync-core-coding-standards.mjs
8
- * pnpm exec fg-devkit sync-core-coding-standards
9
- */
10
-
11
- import fs from 'node:fs'
12
- import path from 'node:path'
13
- import { findFgDevkitPackageRoot } from './lib/common.mjs'
14
-
15
- const projectRoot = process.cwd()
16
-
17
- function getConsumerPackageName() {
18
- const pkgPath = path.join(projectRoot, 'package.json')
19
- if (!fs.existsSync(pkgPath)) {
20
- return 'consumer-project'
21
- }
22
- try {
23
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
24
- if (typeof pkg.name === 'string' && pkg.name.trim() !== '') {
25
- return pkg.name.trim()
26
- }
27
- } catch {
28
- // fall through to default
29
- }
30
- return 'consumer-project'
31
- }
32
-
33
- const fgRoot = findFgDevkitPackageRoot(projectRoot)
34
- if (!fgRoot) {
35
- console.error(
36
- '[sync-core-coding-standards] Could not find fg-devkit (install fg-devkit or a package that bundles it, e.g. fds-vue-core).',
37
- )
38
- process.exit(1)
39
- }
40
-
41
- const sourcePath = path.join(fgRoot, '.cursor', 'rules', 'core-coding-standards.mdc')
42
- if (!fs.existsSync(sourcePath)) {
43
- console.error('[sync-core-coding-standards] Missing source rule in fg-devkit package.')
44
- process.exit(1)
45
- }
46
-
47
- const destinationPath = path.join(projectRoot, '.cursor', 'rules', 'core-coding-standards.mdc')
48
- const packageName = getConsumerPackageName()
49
-
50
- try {
51
- const content = fs.readFileSync(sourcePath, 'utf8')
52
- const updated = content.replace(
53
- /^description:\s*.*$/m,
54
- `description: Core coding standards for ${packageName}`,
55
- )
56
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true })
57
- fs.writeFileSync(destinationPath, updated, 'utf8')
58
- console.log(`[sync-core-coding-standards] Updated ${destinationPath}`)
59
- } catch (err) {
60
- console.error(`[sync-core-coding-standards] ${err.message}`)
61
- process.exit(1)
62
- }