From ad66c40314c2a667596cf1fa325346ae47f2f85e Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sun, 19 Apr 2026 21:30:23 -0400 Subject: [PATCH 1/4] wip --- src/lib/css_cache.ts | 6 +++--- src/lib/deps.ts | 32 ++++++++++++++++++-------------- src/lib/deps_defaults.ts | 25 ++++++++++--------------- src/lib/style_rule_parser.ts | 18 +++++++++--------- src/routes/library.json | 16 ++++++++-------- src/test/fixtures/mock_deps.ts | 7 ++++++- 6 files changed, 54 insertions(+), 50 deletions(-) diff --git a/src/lib/css_cache.ts b/src/lib/css_cache.ts index d7620db90..0c00466ee 100644 --- a/src/lib/css_cache.ts +++ b/src/lib/css_cache.ts @@ -110,10 +110,10 @@ export const load_cached_extraction = async ( cache_path: string, ): Promise => { try { - const content = await deps.read_text({path: cache_path}); - if (!content) return null; + const r = await deps.read_text({path: cache_path}); + if (!r.ok) return null; - const cached = JSON.parse(content) as CachedExtraction; + const cached = JSON.parse(r.value) as CachedExtraction; // Invalidate if version mismatch if (cached.v !== CSS_CACHE_VERSION) { diff --git a/src/lib/deps.ts b/src/lib/deps.ts index 4aa094ac6..6cffd8e45 100644 --- a/src/lib/deps.ts +++ b/src/lib/deps.ts @@ -8,17 +8,20 @@ * - Internal functions take `deps` as a required first parameter * - Public APIs (plugin options) default to `default_cache_deps` * - All deps accept a single `options` object parameter - * - All fallible deps return `Result` from `@fuzdev/fuz_util` - * - Never throw `Error` in deps - return `Result` with `ok: false` - * - Use `null` for expected "not found" cases (not errors) + * - All fallible deps return `Result<{value: T}, FsError>` from `@fuzdev/fuz_util` + * - Errors carry a discriminated `kind` so callers branch without string matching * * **Production usage:** * ```typescript * import {default_cache_deps} from './deps_defaults.js'; - * const content = await deps.read_text({path: '/path/to/file.json'}); - * if (!content) { - * // File not found + * const r = await deps.read_text({path: '/path/to/file.json'}); + * if (!r.ok) { + * if (r.kind === 'not_found') { + * // file missing + * } + * return; * } + * const content = r.value; * ``` * * **Test usage:** @@ -36,6 +39,9 @@ */ import type {Result} from '@fuzdev/fuz_util/result.js'; +import type {FsError} from '@fuzdev/fuz_util/fs.js'; + +export type {FsError}; /** * Cache-related file system deps. @@ -48,21 +54,19 @@ import type {Result} from '@fuzdev/fuz_util/result.js'; export interface CacheDeps { /** * Reads a text file. - * Returns `null` if file doesn't exist. + * Returns a `not_found` error if the file doesn't exist. */ - read_text: (options: {path: string}) => Promise; + read_text: (options: {path: string}) => Promise>; /** * Writes a text file atomically (temp file + rename for crash safety). * Creates parent directories if they don't exist. */ - write_text_atomic: (options: { - path: string; - content: string; - }) => Promise>; + write_text_atomic: (options: {path: string; content: string}) => Promise>; /** - * Removes a file. Succeeds silently if file doesn't exist. + * Removes a file. Returns a `not_found` error if the file doesn't exist — + * callers that want `rm -f` semantics should ignore that kind explicitly. */ - unlink: (options: {path: string}) => Promise>; + unlink: (options: {path: string}) => Promise>; } diff --git a/src/lib/deps_defaults.ts b/src/lib/deps_defaults.ts index c64320562..cb5ba1d50 100644 --- a/src/lib/deps_defaults.ts +++ b/src/lib/deps_defaults.ts @@ -10,19 +10,20 @@ import {readFile, writeFile, mkdir, unlink, rename} from 'node:fs/promises'; import {dirname} from 'node:path'; +import type {Result} from '@fuzdev/fuz_util/result.js'; +import {classify_fs_error, type FsError} from '@fuzdev/fuz_util/fs.js'; + import type {CacheDeps} from './deps.js'; /** - * Wraps an async function that returns void, converting exceptions to `Result`. + * Wraps an async void-returning function, converting thrown errors to typed `FsError`. */ -const wrap_void = async ( - fn: () => Promise, -): Promise<{ok: true} | {ok: false; message: string}> => { +const wrap_void = async (fn: () => Promise): Promise> => { try { await fn(); return {ok: true}; } catch (error) { - return {ok: false, message: error instanceof Error ? error.message : String(error)}; + return {ok: false, ...classify_fs_error(error)}; } }; @@ -32,9 +33,9 @@ const wrap_void = async ( export const default_cache_deps: CacheDeps = { read_text: async ({path}) => { try { - return await readFile(path, 'utf8'); - } catch { - return null; + return {ok: true, value: await readFile(path, 'utf8')}; + } catch (error) { + return {ok: false, ...classify_fs_error(error)}; } }, @@ -49,11 +50,5 @@ export const default_cache_deps: CacheDeps = { }); }, - unlink: async ({path}) => { - return wrap_void(async () => { - await unlink(path).catch(() => { - // Ignore if already gone - }); - }); - }, + unlink: async ({path}) => wrap_void(() => unlink(path)), }; diff --git a/src/lib/style_rule_parser.ts b/src/lib/style_rule_parser.ts index 4cf8336c0..f5a9e3b18 100644 --- a/src/lib/style_rule_parser.ts +++ b/src/lib/style_rule_parser.ts @@ -499,12 +499,12 @@ export const load_style_rule_index = async ( style_css_path?: string, ): Promise => { const path = style_css_path ?? new URL('./style.css', import.meta.url).pathname; - const css = await deps.read_text({path}); - if (css === null) { - throw new Error(`Failed to read style.css from ${path}`); + const r = await deps.read_text({path}); + if (!r.ok) { + throw new Error(`Failed to read style.css from ${path}: ${r.message}`); } - const content_hash = hash_blake3(css); - return parse_style_css(css, content_hash); + const content_hash = hash_blake3(r.value); + return parse_style_css(r.value, content_hash); }; /** @@ -531,11 +531,11 @@ export const load_default_style_css = async ( style_css_path?: string, ): Promise => { const path = style_css_path ?? new URL('./style.css', import.meta.url).pathname; - const css = await deps.read_text({path}); - if (css === null) { - throw new Error(`Failed to read style.css from ${path}`); + const r = await deps.read_text({path}); + if (!r.ok) { + throw new Error(`Failed to read style.css from ${path}: ${r.message}`); } - return css; + return r.value; }; /** diff --git a/src/routes/library.json b/src/routes/library.json index 89f45fc27..244565023 100644 --- a/src/routes/library.json +++ b/src/routes/library.json @@ -2844,7 +2844,7 @@ "name": "default_cache_deps", "kind": "variable", "doc_comment": "Default filesystem deps using `node:fs/promises`.", - "source_line": 32, + "source_line": 33, "type_signature": "CacheDeps" } ], @@ -2858,31 +2858,31 @@ "name": "CacheDeps", "kind": "type", "doc_comment": "Cache-related file system deps.\nAbstracted to enable test isolation from the actual filesystem.\n\nNamed `CacheDeps` (not `FsDeps`) because it only covers\nthe specific deps needed for cache management, not general\nfilesystem access.", - "source_line": 48, + "source_line": 54, "type_signature": "CacheDeps", "properties": [ { "name": "read_text", "kind": "variable", - "type_signature": "(options: {path: string}) => Promise", - "doc_comment": "Reads a text file.\nReturns `null` if file doesn't exist." + "type_signature": "(options: {path: string}) => Promise>", + "doc_comment": "Reads a text file.\nReturns a `not_found` error if the file doesn't exist." }, { "name": "write_text_atomic", "kind": "variable", - "type_signature": "(options: {\n\t\tpath: string;\n\t\tcontent: string;\n\t}) => Promise>", + "type_signature": "(options: {path: string; content: string}) => Promise>", "doc_comment": "Writes a text file atomically (temp file + rename for crash safety).\nCreates parent directories if they don't exist." }, { "name": "unlink", "kind": "variable", - "type_signature": "(options: {path: string}) => Promise>", - "doc_comment": "Removes a file. Succeeds silently if file doesn't exist." + "type_signature": "(options: {path: string}) => Promise>", + "doc_comment": "Removes a file. Returns a `not_found` error if the file doesn't exist —\ncallers that want `rm -f` semantics should ignore that kind explicitly." } ] } ], - "module_comment": "Deps interfaces for dependency injection.\n\nThis is the core pattern enabling testability without mocks.\nAll file system side effects are abstracted into interfaces.\n\n**Design principles:**\n- Internal functions take `deps` as a required first parameter\n- Public APIs (plugin options) default to `default_cache_deps`\n- All deps accept a single `options` object parameter\n- All fallible deps return `Result` from `@fuzdev/fuz_util`\n- Never throw `Error` in deps - return `Result` with `ok: false`\n- Use `null` for expected \"not found\" cases (not errors)\n\n**Production usage:**\n```typescript\nimport {default_cache_deps} from './deps_defaults.js';\nconst content = await deps.read_text({path: '/path/to/file.json'});\nif (!content) {\n // File not found\n}\n```\n\n**Test usage:**\n```typescript\nimport {create_mock_cache_deps, create_mock_fs_state} from '../test/fixtures/mock_deps.js';\nconst state = create_mock_fs_state();\nconst deps = create_mock_cache_deps(state);\n// Use deps in tests - all operations are in-memory\n```\n\nSee `deps_defaults.ts` for real implementations.\nSee `test/fixtures/mock_deps.ts` for mock implementations." + "module_comment": "Deps interfaces for dependency injection.\n\nThis is the core pattern enabling testability without mocks.\nAll file system side effects are abstracted into interfaces.\n\n**Design principles:**\n- Internal functions take `deps` as a required first parameter\n- Public APIs (plugin options) default to `default_cache_deps`\n- All deps accept a single `options` object parameter\n- All fallible deps return `Result<{value: T}, FsError>` from `@fuzdev/fuz_util`\n- Errors carry a discriminated `kind` so callers branch without string matching\n\n**Production usage:**\n```typescript\nimport {default_cache_deps} from './deps_defaults.js';\nconst r = await deps.read_text({path: '/path/to/file.json'});\nif (!r.ok) {\n if (r.kind === 'not_found') {\n // file missing\n }\n return;\n}\nconst content = r.value;\n```\n\n**Test usage:**\n```typescript\nimport {create_mock_cache_deps, create_mock_fs_state} from '../test/fixtures/mock_deps.js';\nconst state = create_mock_fs_state();\nconst deps = create_mock_cache_deps(state);\n// Use deps in tests - all operations are in-memory\n```\n\nSee `deps_defaults.ts` for real implementations.\nSee `test/fixtures/mock_deps.ts` for mock implementations." }, { "path": "diagnostics.ts", diff --git a/src/test/fixtures/mock_deps.ts b/src/test/fixtures/mock_deps.ts index cc99018ab..ace5664e4 100644 --- a/src/test/fixtures/mock_deps.ts +++ b/src/test/fixtures/mock_deps.ts @@ -30,7 +30,9 @@ export const create_mock_fs_state = (): MockFsState => ({ */ export const create_mock_cache_deps = (state: MockFsState): CacheDeps => ({ read_text: async ({path}) => { - return state.files.get(path) ?? null; + const value = state.files.get(path); + if (value === undefined) return {ok: false, kind: 'not_found', message: `not found: ${path}`}; + return {ok: true, value}; }, write_text_atomic: async ({path, content}) => { @@ -39,6 +41,9 @@ export const create_mock_cache_deps = (state: MockFsState): CacheDeps => ({ }, unlink: async ({path}) => { + if (!state.files.has(path)) { + return {ok: false, kind: 'not_found', message: `not found: ${path}`}; + } state.files.delete(path); return {ok: true}; }, From 952b86937970e7f0f8596492b371b3bf790d03db Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Mon, 20 Apr 2026 11:23:13 -0400 Subject: [PATCH 2/4] wip --- package-lock.json | 36 ++++++++++++++++++------------------ package.json | 10 +++++----- src/lib/deps_defaults.ts | 6 +++--- src/routes/library.json | 10 +++++----- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 085e3662b..86f811b5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,11 @@ "@changesets/changelog-git": "^0.2.1", "@fuzdev/blake3_wasm": "^0.1.0", "@fuzdev/fuz_code": "^0.45.1", - "@fuzdev/fuz_ui": "^0.191.3", - "@fuzdev/fuz_util": "^0.56.0", - "@fuzdev/gro": "^0.197.2", + "@fuzdev/fuz_ui": "^0.191.4", + "@fuzdev/fuz_util": "^0.57.0", + "@fuzdev/gro": "^0.198.0", "@jridgewell/trace-mapping": "^0.3.31", - "@ryanatkn/eslint-config": "^0.10.1", + "@ryanatkn/eslint-config": "^0.11.0", "@sveltejs/acorn-typescript": "^1.0.9", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.53.4", @@ -51,7 +51,7 @@ }, "peerDependencies": { "@fuzdev/blake3_wasm": "^0.1.0", - "@fuzdev/fuz_util": ">=0.52.0", + "@fuzdev/fuz_util": ">=0.57.0", "@fuzdev/gro": ">=0.195.0", "@sveltejs/acorn-typescript": "^1", "@webref/css": "^8", @@ -838,9 +838,9 @@ } }, "node_modules/@fuzdev/fuz_ui": { - "version": "0.191.3", - "resolved": "https://registry.npmjs.org/@fuzdev/fuz_ui/-/fuz_ui-0.191.3.tgz", - "integrity": "sha512-VKKguEpQnItnlvPfO3bwayra1APf6IaUrKC3kGxoXsU1w3xWCr0FZEfbj0x2pc/PSFuZH7qbA0FY+hBV3gIqMA==", + "version": "0.191.4", + "resolved": "https://registry.npmjs.org/@fuzdev/fuz_ui/-/fuz_ui-0.191.4.tgz", + "integrity": "sha512-OYF6k1GR2v2wy5BbnYZ6GRGCloS3zQ5y+Nvn1trhN38YdX5HPgBDRWUOUdI9EbHCiMb6t0Ey+dUuEqu97OQFlg==", "dev": true, "license": "MIT", "engines": { @@ -894,9 +894,9 @@ } }, "node_modules/@fuzdev/fuz_util": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/@fuzdev/fuz_util/-/fuz_util-0.56.0.tgz", - "integrity": "sha512-+5YQQRF/bheWQ2t9BSgwkjqx8pHdpT7KdJLWD10d+9n9HQTMkzK+qmN86pyD6dWGUByjSPe7rZZirZFKCKqz5w==", + "version": "0.57.0", + "resolved": "https://registry.npmjs.org/@fuzdev/fuz_util/-/fuz_util-0.57.0.tgz", + "integrity": "sha512-ti/na0PnROtZMVGuh4P1j0mPg7h7Kh9Eqn4mLoKwuyGTVwMllm/1ilzriGdhG2pYytkZVNv/Ow5w5cyybRawqg==", "dev": true, "license": "MIT", "engines": { @@ -935,9 +935,9 @@ } }, "node_modules/@fuzdev/gro": { - "version": "0.197.2", - "resolved": "https://registry.npmjs.org/@fuzdev/gro/-/gro-0.197.2.tgz", - "integrity": "sha512-YwMXDcegJAxMv1Ftm0lAQNnFUsPe64fEeW09xeNc4nl0B4GbDaeO4NZwVU+kKa6GHnL9AmpIZPnvsa5UYJwSng==", + "version": "0.198.0", + "resolved": "https://registry.npmjs.org/@fuzdev/gro/-/gro-0.198.0.tgz", + "integrity": "sha512-P3euxU6AoZh4Zh5Urd2AGkgZj13sEArjceWKQNIr/WrxVk5WcIVpkxXflHZ8+7bDNeTTJdsl4kwdLVqpWmwBOA==", "dev": true, "license": "MIT", "dependencies": { @@ -965,7 +965,7 @@ }, "peerDependencies": { "@fuzdev/blake3_wasm": "^0.1.0", - "@fuzdev/fuz_util": ">=0.53.0", + "@fuzdev/fuz_util": ">=0.57.0", "@sveltejs/kit": "^2", "esbuild": "^0.27.0", "svelte": "^5", @@ -1697,9 +1697,9 @@ ] }, "node_modules/@ryanatkn/eslint-config": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@ryanatkn/eslint-config/-/eslint-config-0.10.1.tgz", - "integrity": "sha512-fHQ5PyFriflVj/fiF9m4SoUnipyK/Of522HL3+YA5TD2lKdJueA5c4wxucxkuFanuZ1FvsCBjGN/wMHO94HNHA==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@ryanatkn/eslint-config/-/eslint-config-0.11.0.tgz", + "integrity": "sha512-Dn6PoPH+hQ2RkjkGpnV7ki49yqqk2oQz0Ii+nV7TNiwAdK+S0f+dHLD89rZectyOD/aQ6mU+LxzVP8Ol8J1dVg==", "dev": true, "license": "Unlicense", "dependencies": { diff --git a/package.json b/package.json index 91af9d6ee..561c2c9a0 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ }, "peerDependencies": { "@fuzdev/blake3_wasm": "^0.1.0", - "@fuzdev/fuz_util": ">=0.52.0", + "@fuzdev/fuz_util": ">=0.57.0", "@fuzdev/gro": ">=0.195.0", "@sveltejs/acorn-typescript": "^1", "@webref/css": "^8", @@ -69,11 +69,11 @@ "@changesets/changelog-git": "^0.2.1", "@fuzdev/blake3_wasm": "^0.1.0", "@fuzdev/fuz_code": "^0.45.1", - "@fuzdev/fuz_ui": "^0.191.3", - "@fuzdev/fuz_util": "^0.56.0", - "@fuzdev/gro": "^0.197.2", + "@fuzdev/fuz_ui": "^0.191.4", + "@fuzdev/fuz_util": "^0.57.0", + "@fuzdev/gro": "^0.198.0", "@jridgewell/trace-mapping": "^0.3.31", - "@ryanatkn/eslint-config": "^0.10.1", + "@ryanatkn/eslint-config": "^0.11.0", "@sveltejs/acorn-typescript": "^1.0.9", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.53.4", diff --git a/src/lib/deps_defaults.ts b/src/lib/deps_defaults.ts index cb5ba1d50..8a64e850c 100644 --- a/src/lib/deps_defaults.ts +++ b/src/lib/deps_defaults.ts @@ -11,7 +11,7 @@ import {readFile, writeFile, mkdir, unlink, rename} from 'node:fs/promises'; import {dirname} from 'node:path'; import type {Result} from '@fuzdev/fuz_util/result.js'; -import {classify_fs_error, type FsError} from '@fuzdev/fuz_util/fs.js'; +import {fs_classify_error, type FsError} from '@fuzdev/fuz_util/fs.js'; import type {CacheDeps} from './deps.js'; @@ -23,7 +23,7 @@ const wrap_void = async (fn: () => Promise): Promise=0.52.0", + "@fuzdev/fuz_util": ">=0.57.0", "@fuzdev/gro": ">=0.195.0", "@sveltejs/acorn-typescript": "^1", "@webref/css": "^8", @@ -80,11 +80,11 @@ "@changesets/changelog-git": "^0.2.1", "@fuzdev/blake3_wasm": "^0.1.0", "@fuzdev/fuz_code": "^0.45.1", - "@fuzdev/fuz_ui": "^0.191.3", - "@fuzdev/fuz_util": "^0.56.0", - "@fuzdev/gro": "^0.197.2", + "@fuzdev/fuz_ui": "^0.191.4", + "@fuzdev/fuz_util": "^0.57.0", + "@fuzdev/gro": "^0.198.0", "@jridgewell/trace-mapping": "^0.3.31", - "@ryanatkn/eslint-config": "^0.10.1", + "@ryanatkn/eslint-config": "^0.11.0", "@sveltejs/acorn-typescript": "^1.0.9", "@sveltejs/adapter-static": "^3.0.10", "@sveltejs/kit": "^2.53.4", From b705251d7bf7d29a6145485adc25095cfc36973c Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Mon, 20 Apr 2026 11:25:43 -0400 Subject: [PATCH 3/4] wip --- package-lock.json | 8 ++++---- package.json | 2 +- src/lib/css_class_generation.ts | 1 - src/routes/library.json | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86f811b5b..d1b78265a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@changesets/changelog-git": "^0.2.1", - "@fuzdev/blake3_wasm": "^0.1.0", + "@fuzdev/blake3_wasm": "^0.1.1", "@fuzdev/fuz_code": "^0.45.1", "@fuzdev/fuz_ui": "^0.191.4", "@fuzdev/fuz_util": "^0.57.0", @@ -737,9 +737,9 @@ } }, "node_modules/@fuzdev/blake3_wasm": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@fuzdev/blake3_wasm/-/blake3_wasm-0.1.0.tgz", - "integrity": "sha512-EU5uUcSX55Li3IXi1NiBDoVlxCN8ip9wqAhVZlMBEUa+cFQtLL6Z8GpYjlWy0KosLmxy2Z9WQv49PAkiAzFppg==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fuzdev/blake3_wasm/-/blake3_wasm-0.1.1.tgz", + "integrity": "sha512-JikFOouJEVLKJvsEQ7+fRdo3GElL4nmu2sV8rg+xu2bv+BAMk+GvoO3TOSPYX9fdHeXJ7U4N0IdIP/mNh7WNfw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 561c2c9a0..5b3c9007e 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ }, "devDependencies": { "@changesets/changelog-git": "^0.2.1", - "@fuzdev/blake3_wasm": "^0.1.0", + "@fuzdev/blake3_wasm": "^0.1.1", "@fuzdev/fuz_code": "^0.45.1", "@fuzdev/fuz_ui": "^0.191.4", "@fuzdev/fuz_util": "^0.57.0", diff --git a/src/lib/css_class_generation.ts b/src/lib/css_class_generation.ts index 9770da1fb..2088fedac 100644 --- a/src/lib/css_class_generation.ts +++ b/src/lib/css_class_generation.ts @@ -364,7 +364,6 @@ export const generate_classes_css = ( } } else if ('ruleset' in v) { // Check for empty ruleset - // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (!v.ruleset || !v.ruleset.trim()) { diagnostics.push({ phase: 'generation', diff --git a/src/routes/library.json b/src/routes/library.json index ea174051e..7ad7bc0ca 100644 --- a/src/routes/library.json +++ b/src/routes/library.json @@ -78,7 +78,7 @@ }, "devDependencies": { "@changesets/changelog-git": "^0.2.1", - "@fuzdev/blake3_wasm": "^0.1.0", + "@fuzdev/blake3_wasm": "^0.1.1", "@fuzdev/fuz_code": "^0.45.1", "@fuzdev/fuz_ui": "^0.191.4", "@fuzdev/fuz_util": "^0.57.0", From dfb55110e6b5d56ce13df10606d69b8ba46120c0 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Mon, 20 Apr 2026 11:26:44 -0400 Subject: [PATCH 4/4] wip --- .changeset/shaggy-birds-lick.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/shaggy-birds-lick.md diff --git a/.changeset/shaggy-birds-lick.md b/.changeset/shaggy-birds-lick.md new file mode 100644 index 000000000..90b3cf800 --- /dev/null +++ b/.changeset/shaggy-birds-lick.md @@ -0,0 +1,5 @@ +--- +'@fuzdev/fuz_css': minor +--- + +upgrade fuz_util