Skip to content

Commit aad1fce

Browse files
antfubotantfu
andauthored
chore: enable @antfu/eslint-config anti-slop and fix lint (#333)
* chore: enable @antfu/eslint-config anti-slop and fix lint Upgrade @antfu/eslint-config to 9.5.1, install the eslint-plugin-slop and eslint-plugin-sonarjs peers, and enable the anti-slop rule set with full-file inspection. Parse CSS/HTML/Svelte and Vue style blocks as plain text so the universal no-em-dash rule reads their raw content instead of erroring, then resolve every reported violation across the codebase: replace em dashes with the house-style spaced hyphen, drop chained type assertions, trim overlong comments, and reduce cognitive complexity by extracting helpers. Created with the help of an agent. * docs: rework former em-dash phrasing into natural prose Replace the mechanical em-dash-to-hyphen substitution with proper rewrites: each former em dash becomes a colon, comma, semicolon, parentheses, a split sentence, or a connective, chosen for natural reading. Covers docs, comments, JSDoc, and human-facing strings across the codebase. Created with the help of an agent. * chore: update snapshot * fix: restore revoke null-safety and refresh API snapshots Restore the optional-chaining guard in revokeActiveConnectionsForToken: an earlier assertion cleanup dropped it, so a context without an rpc host threw "Cannot read properties of undefined (reading '_rpcGroup')" instead of returning early (surfaced by host-docks.test.ts). Update the tsnapi dts snapshots for devframe/internal and @devframes/json-render node so the diagnostic `fix` literal types match the reworded messages. Created with the help of an agent. --------- Co-authored-by: Anthony Fu <github@antfu.me>
1 parent a1eb300 commit aad1fce

809 files changed

Lines changed: 7127 additions & 5721 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Ahead-of-time build artifacts that live under `src/` - the shadow-root styleshee
5858
## Conventions
5959

6060
- RPC functions must use `defineRpcFunction`; always namespace IDs `devframes:plugin:<slug>:<fn-name>` for built-in devframes (the literal `plugin:` token mirrors the `@devframes/plugin-<slug>` package name on the wire - it is npm namespacing, not a concept).
61-
- **No magic event names — use the centralized event maps.** Every event, broadcast, shared-state key, and channel name lives in one of two source-of-truth maps: `DEVFRAME_EVENTS` (`packages/devframe/src/events.ts`, re-exported from `devframe/constants`) for the core runtime, and `HUB_EVENTS` (`packages/hub/src/events.ts`, re-exported from `@devframes/hub/constants`) for the hub. Reference `DEVFRAME_EVENTS.*` / `HUB_EVENTS.*` at call sites (`.events.emit`/`.on`, `rpc.broadcast({ method })`, `sharedState.get(key)`, `defineHubRpcFunction({ name })`, `rpc.call`) instead of re-typing a string literal. The two maps and the [`docs/content/8.references/3.events.md`](docs/content/8.references/3.events.md) Events Reference are kept in lockstep: adding, renaming, or removing a name means editing the map **and** that page in the same change — every name in the maps appears in the tables, and vice versa. The only literals left are unavoidable type-position keys (the `EventEmitter<…>` maps in `types/*` and the `DevframeRpcClientFunctions`/`DevframeRpcServerFunctions` augmentations), which mirror the maps; a package that deliberately avoids a hub dependency (e.g. `@devframes/plugin-terminals`, which models the hub bridge structurally) keeps a local literal rather than importing `HUB_EVENTS`.
61+
- **No magic event names: use the centralized event maps.** Every event, broadcast, shared-state key, and channel name lives in one of two source-of-truth maps: `DEVFRAME_EVENTS` (`packages/devframe/src/events.ts`, re-exported from `devframe/constants`) for the core runtime, and `HUB_EVENTS` (`packages/hub/src/events.ts`, re-exported from `@devframes/hub/constants`) for the hub. Reference `DEVFRAME_EVENTS.*` / `HUB_EVENTS.*` at call sites (`.events.emit`/`.on`, `rpc.broadcast({ method })`, `sharedState.get(key)`, `defineHubRpcFunction({ name })`, `rpc.call`) instead of re-typing a string literal. The two maps and the [`docs/content/8.references/3.events.md`](docs/content/8.references/3.events.md) Events Reference are kept in lockstep: adding, renaming, or removing a name means editing the map **and** that page in the same change; every name in the maps appears in the tables, and vice versa. The only literals left are unavoidable type-position keys (the `EventEmitter<…>` maps in `types/*` and the `DevframeRpcClientFunctions`/`DevframeRpcServerFunctions` augmentations), which mirror the maps; a package that deliberately avoids a hub dependency (e.g. `@devframes/plugin-terminals`, which models the hub bridge structurally) keeps a local literal rather than importing `HUB_EVENTS`.
6262
- **Stay validator-neutral.** `devframe` and every `@devframes/*` package must not introduce a preferred schema validator dependency - no `valibot`, `zod`, `arktype`, etc. in their runtime `dependencies`. `args`/`returns`/flag schemas are typed against [Standard Schema](https://standardschema.dev/) (`@standard-schema/spec`, types-only); first-party code that needs to author a schema uses the built-in zero-dep `devframe/utils/simple-schema` builder (deliberately minimal - not a general validator). JSON-schema conversion uses each schema's own Standard JSON Schema converter (`~standard.jsonSchema`, implemented by e.g. zod 4) when present and degrades to a permissive object otherwise - no converter library and no vendor dependency is required. Docs, by contrast, should point *users* at a real validator for their own integrations - recommend **valibot** (lightest) or **zod** (worth reusing if they already pull it via the JSON-render or MCP integrations).
6363
- Shared state via `devframe/utils/shared-state`; keep values serializable.
6464
- Utility imports use the package-path form `devframe/utils/*`, never relative `../utils/*`.

bump.config.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import { syncStarterVersion } from './scripts/sync-starter-version.ts'
55

66
export default defineConfig({
77
all: true,
8-
// `starter/` pins real `devframe`/`@devframes/*` versions (it's a
9-
// copy-paste-ready template, not a workspace member consuming
10-
// `catalog:`/`workspace:*`), so `bumpp -r` can't reach it on its own -
11-
// sync it here, before the version-bump commit is made.
8+
/**
9+
* `starter/` pins real `devframe`/`@devframes/*` versions (it's a
10+
* copy-paste-ready template, not a workspace member consuming
11+
* `catalog:`/`workspace:*`), so `bumpp -r` can't reach it on its own -
12+
* sync it here, before the version-bump commit is made.
13+
*/
1214
execute: async (operation) => {
1315
await syncStarterVersion(operation.state.newVersion)
1416
await x('pnpm', ['install', '--frozen-lockfile=false'], { nodeOptions: { stdio: 'inherit', cwd: process.cwd() } })

design/build-shadow-css.ts

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface BuildShadowCssOptions {
3838
userStylePath?: string | readonly string[]
3939
/**
4040
* Prefix Wind's `--un-*` custom properties are renamed to (see
41-
* `namespaceShadowCssVars`) unique per shadow-root surface so two
41+
* `namespaceShadowCssVars`), unique per shadow-root surface so two
4242
* shadow trees on the same host page never collide.
4343
*/
4444
varPrefix: string
@@ -51,15 +51,17 @@ export interface BuildShadowCssResult {
5151
css: string
5252
}
5353

54-
// Compile a shadow-root surface's UnoCSS output ahead of time into a plain
55-
// string module (`<srcDir>/.generated/css.ts`) that the surface adopts into
56-
// its shadow root — fully styled inside any host page without a global
57-
// stylesheet, and immune to the host page's own styles leaking in. Shared by
58-
// `@devframes/hub-ui`'s dock and `@devframes/json-render-ui`'s renderer
59-
// module: same pipeline, same two shadow-root gotchas (see the root
60-
// AGENTS.md "Design system" section), different source globs. Writes the
61-
// generated file itself; returns stats so each caller (a `scripts/` entry,
62-
// exempt from the `no-console` lint rule) prints its own summary line.
54+
/**
55+
* Compile a shadow-root surface's UnoCSS output ahead of time into a plain
56+
* string module (`<srcDir>/.generated/css.ts`) that the surface adopts into
57+
* its shadow root, fully styled inside any host page without a global
58+
* stylesheet, and immune to the host page's own styles leaking in. Shared by
59+
* `@devframes/hub-ui`'s dock and `@devframes/json-render-ui`'s renderer
60+
* module: same pipeline, same two shadow-root gotchas (see the root
61+
* AGENTS.md "Design system" section), different source globs. Writes the
62+
* generated file itself; returns stats so each caller (a `scripts/` entry,
63+
* exempt from the `no-console` lint rule) prints its own summary line.
64+
*/
6365
export async function buildShadowCss(options: BuildShadowCssOptions): Promise<BuildShadowCssResult> {
6466
const { srcDir, globs, config, primaryRampPath, userStylePath, varPrefix } = options
6567
const generatedCss = join(srcDir, '.generated/css.ts')
@@ -75,7 +77,7 @@ export async function buildShadowCss(options: BuildShadowCssOptions): Promise<Bu
7577
// Shadow-root surfaces reuse `@antfu/design`'s Vue components (buttons,
7678
// badges, …) directly. UnoCSS ignores `node_modules` by default, so their
7779
// semantic shortcut classes (`btn-primary`, `btn-action`, `badge-*`, …)
78-
// would be absent from the shadow-root stylesheet scan the design
80+
// would be absent from the shadow-root stylesheet, so scan the design
7981
// package's component sources too so those classes ship in the injected
8082
// CSS.
8183
const designComponentsDir = join(require.resolve('@antfu/design/package.json'), '..', 'components')
@@ -109,12 +111,12 @@ export async function buildShadowCss(options: BuildShadowCssOptions): Promise<Bu
109111
const unoResult = await generator.generate(tokens)
110112
// Wind3 drops a *plain* semantic shortcut (`.bg-base` / `.color-base`) from
111113
// the main pass when the same shortcut also appears variant-prefixed in the
112-
// sources (e.g. `@antfu/design`'s Tabs emits `data-[state=active]:bg-base`)
114+
// sources (e.g. `@antfu/design`'s Tabs emits `data-[state=active]:bg-base`),
113115
// a shortcut+variant interaction. Generate the shadow-surface tokens in a
114116
// dedicated pass so their plain (and `.dark`) rules are always present.
115117
const surfaces = await generator.generate(shadowSurfaceSafelist.join(' '))
116118
// Wind3 bakes the `primary` theme color to literal `rgb()` triplets at
117-
// generate-time rewire them to read the live `--colors-primary-*`
119+
// generate-time, so rewire them to read the live `--colors-primary-*`
118120
// variables `primary-ramp.css` derives from `--devframe-primary`, so a
119121
// rebrand actually retints `text-primary`/`bg-primary`/`btn-primary`/…
120122
// (see `rewireBakedPrimaryColors`'s own comment).

design/design.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,10 @@ export function navBrand(extra?: string): string {
8989
return cx('flex items-center gap-1.5 shrink-0 font-semibold text-sm select-none', extra)
9090
}
9191

92-
// Mirrors devframe's `DevframeConnectionStatus` (kept local so this class-helper
93-
// module stays free of package imports); the two share the same string members.
92+
/**
93+
* Mirrors devframe's `DevframeConnectionStatus` (kept local so this class-helper
94+
* module stays free of package imports); the two share the same string members.
95+
*/
9496
export type ConnectionStatus = 'connecting' | 'connected' | 'unauthorized' | 'disconnected' | 'error'
9597

9698
export interface ConnectionIndicator {
@@ -109,9 +111,11 @@ const CONNECTION_TONE: Record<Exclude<ConnectionStatus, 'connected'>, { label: s
109111
error: { label: 'error', dot: 'bg-error' },
110112
}
111113

112-
// The shared top-nav connection indicator: a small status dot + label. Returns
113-
// `null` when the client is `connected`, so every surface renders the indicator
114-
// only while the connection is not live.
114+
/**
115+
* The shared top-nav connection indicator: a small status dot + label. Returns
116+
* `null` when the client is `connected`, so every surface renders the indicator
117+
* only while the connection is not live.
118+
*/
115119
export function connectionIndicator(status: ConnectionStatus, extra?: string): ConnectionIndicator | null {
116120
if (status === 'connected')
117121
return null
@@ -167,18 +171,22 @@ const CONNECTION_STATE: Record<Exclude<ConnectionStatus, 'connected'>, Connectio
167171
},
168172
}
169173

170-
// The shared full-panel connection state copy: shown whenever the client isn't
171-
// `connected`, so a surface never sits on an infinite spinner without saying
172-
// why. Returns `null` when connected. Pair with the `connection*` class builders
173-
// below so every surface renders the identical centered glyph + title + body.
174+
/**
175+
* The shared full-panel connection state copy: shown whenever the client isn't
176+
* `connected`, so a surface never sits on an infinite spinner without saying
177+
* why. Returns `null` when connected. Pair with the `connection*` class builders
178+
* below so every surface renders the identical centered glyph + title + body.
179+
*/
174180
export function connectionState(status: ConnectionStatus): ConnectionStateCopy | null {
175181
if (status === 'connected')
176182
return null
177183
return CONNECTION_STATE[status]
178184
}
179185

180-
// Centered fill for the full-panel state; each surface adds its own fill
181-
// strategy (`h-full`, `h-svh w-full`, `absolute inset-0`, …) via `extra`.
186+
/**
187+
* Centered fill for the full-panel state; each surface adds its own fill
188+
* strategy (`h-full`, `h-svh w-full`, `absolute inset-0`, …) via `extra`.
189+
*/
182190
export function connectionPanel(extra?: string): string {
183191
return cx('flex flex-col items-center justify-center gap-4 bg-base p-8 text-center', extra)
184192
}

design/dock-icon.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// `ph:git-branch-duotone`) to its live, sanitized SVG markup, fetched from the
66
// public `api.iconify.design` CDN. Unlike a UnoCSS `preset-icons` class, this
77
// needs no `@iconify-json/*` collection installed and no hand-maintained
8-
// id -> class table any Iconify id just works, at the cost of a network
8+
// id -> class table, since any Iconify id just works, at the cost of a network
99
// round-trip on first render. We reuse @antfu/design's own fetcher, cache and
1010
// sanitizer (`utils/iconify.ts`) rather than reimplementing them; only the id
1111
// parsing and light/dark selection below are devframe-specific, mirroring the
@@ -19,7 +19,7 @@ const ICONIFY_ID = /^(?:i-)?([\w-]+):([\w-]+)$/
1919

2020
/**
2121
* Resolve a dock icon (a `collection:icon` string, or a `{ light, dark }`
22-
* pair — the `light` variant is fetched) to its sanitized SVG markup.
22+
* pair whose `light` variant is fetched) to its sanitized SVG markup.
2323
*
2424
* Returns `undefined` when the id doesn't parse or the fetch fails, so the
2525
* caller can fall back to a text initial.

design/uno.config.ts

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,27 @@ export interface CreateDesignConfigOptions {
1717
* json-render renderer module) pass `presetWind3()` instead: Wind4 registers
1818
* its theme + `--un-*` custom properties via `@property { inherits: false }`
1919
* and keeps them in a document `:root {}` block, neither of which reaches a
20-
* shadow tree so its `color-mix(var(--colors-*))` utilities resolve to
20+
* shadow tree, so its `color-mix(var(--colors-*))` utilities resolve to
2121
* nothing there. Wind3 bakes the same `@antfu/design` semantic utilities to
2222
* concrete `rgb()` + `.dark` variants, which are self-contained inside a
2323
* shadow root.
2424
*/
2525
base?: Preset<any> | Preset<any>[]
2626
}
2727

28-
// Shared devframe UnoCSS base. Every plugin and example composes `@antfu/design`
29-
// the same way — its preset (tuned to devframe's sage green) over a Wind base,
30-
// Phosphor icons, DM Sans/Mono web fonts, and the directive/variant-group
31-
// transformers — so the surfaces look and feel like one product across
32-
// frameworks. Each app extends this via `mergeConfigs([designConfig, { … }])`
33-
// and contributes only its own extraction globs (and any safelist).
34-
//
35-
// The shared web fonts (`sans`/`mono`), the named `z-*` layers and the `h-nav`
36-
// navbar height live here so every surface shares one font stack, one z-index
37-
// scale and one fixed navbar height. The `@antfu/design` preset blocks plain
38-
// `z-<number>`, so the layers are named on purpose.
28+
/**
29+
* Shared devframe UnoCSS base. Every plugin and example composes `@antfu/design`
30+
* the same way: its preset (tuned to devframe's sage green) over a Wind base,
31+
* Phosphor icons, DM Sans/Mono web fonts, and the directive/variant-group
32+
* transformers, so the surfaces look and feel like one product across
33+
* frameworks. Each app extends this via `mergeConfigs([designConfig, { … }])`
34+
* and contributes only its own extraction globs (and any safelist).
35+
*
36+
* The shared web fonts (`sans`/`mono`), the named `z-*` layers and the `h-nav`
37+
* navbar height live here so every surface shares one font stack, one z-index
38+
* scale and one fixed navbar height. The `@antfu/design` preset blocks plain
39+
* `z-<number>`, so the layers are named on purpose.
40+
*/
3941
export function createDesignConfig(options: CreateDesignConfigOptions = {}) {
4042
const base = options.base ?? presetWind4()
4143
return defineConfig({
@@ -45,19 +47,23 @@ export function createDesignConfig(options: CreateDesignConfigOptions = {}) {
4547
presetIcons({ scale: 1.1 }),
4648
],
4749
transformers: [transformerDirectives(), transformerVariantGroup()],
48-
// The shared class-helper builders (`design/design.ts`) assemble their class
49-
// chains at runtime, so every app scans that one file (it carries
50-
// `@unocss-include`) for extraction regardless of its own framework globs.
50+
/**
51+
* The shared class-helper builders (`design/design.ts`) assemble their class
52+
* chains at runtime, so every app scans that one file (it carries
53+
* `@unocss-include`) for extraction regardless of its own framework globs.
54+
*/
5155
content: {
5256
filesystem: [fileURLToPath(new URL('./design.ts', import.meta.url))],
5357
},
54-
// Wind leaves bare `border`/`border-b` at currentColor; restore the subtle
55-
// shared border color (matching `border-base`) for unqualified borders.
58+
/**
59+
* Wind leaves bare `border`/`border-b` at currentColor; restore the subtle
60+
* shared border color (matching `border-base`) for unqualified borders.
61+
*/
5662
preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }],
5763
shortcuts: {
58-
// Fixed navbar height, shared by every surface's top nav.
64+
/** Fixed navbar height, shared by every surface's top nav. */
5965
'h-nav': 'h-10',
60-
// Named z-index layers, shared across every surface.
66+
/** Named z-index layers, shared across every surface. */
6167
'z-nav': 'z-[30]',
6268
'z-dropdown': 'z-[40]',
6369
'z-tooltip': 'z-[45]',
@@ -70,7 +76,7 @@ export function createDesignConfig(options: CreateDesignConfigOptions = {}) {
7076
})
7177
}
7278

73-
// The default shared base (Wind4), consumed by every plugin and example.
79+
/** The default shared base (Wind4), consumed by every plugin and example. */
7480
export const designConfig = createDesignConfig()
7581

7682
/**
@@ -91,7 +97,7 @@ export const designConfig = createDesignConfig()
9197
* declared, so a host page built with Wind4 registers `--un-bg-opacity` /
9298
* `--un-border-opacity` / `--un-text-opacity` (et al.) as
9399
* `@property { syntax: '<percentage>'; inherits: false }` for the whole
94-
* document including inside our shadow tree. Our shadow CSS is Wind3, which
100+
* document, including inside our shadow tree. Our shadow CSS is Wind3, which
95101
* sets those same vars **unitless** (`--un-border-opacity: 0.13`), so the
96102
* global `<percentage>` registration makes every such declaration invalid and
97103
* the dependent `color-mix()` / `rgb(… / var(--un-*))` value collapses (a
@@ -100,9 +106,9 @@ export const designConfig = createDesignConfig()
100106
* The shadow stylesheet sets and reads these vars entirely within itself, so
101107
* renaming every `--un-` to a per-surface prefix (`--un-jr-`, `--un-hub-`)
102108
* keeps it self-consistent while making it immune to whatever the host page
103-
* registered the renamed names are distinct properties the host's
109+
* registered, since the renamed names are distinct properties the host's
104110
* `@property --un-*` rules never match. Apply only to shadow-injected CSS
105-
* (`hub-ui` dock, `json-render-ui` renderer module) the Vite-served SPAs own
111+
* (`hub-ui` dock, `json-render-ui` renderer module); the Vite-served SPAs own
106112
* their whole document and need no rename.
107113
*
108114
* @param css - The compiled shadow-root stylesheet.
@@ -145,15 +151,15 @@ function hexToRgbTriplet(hex: string): string | undefined {
145151
* variables `primary-ramp.css` derives from `--devframe-primary`.
146152
*
147153
* Wind3 (unlike Wind4) resolves each theme color to a literal `rgb(r g b /
148-
* <alpha>)` at compile time the `<alpha>` slot is already dynamic (a slash
154+
* <alpha>)` at compile time, and the `<alpha>` slot is already dynamic (a slash
149155
* literal, or the utility's own `--un-*-opacity` variable), but the base `r g
150156
* b` triplet is baked in, so every `primary`-based utility (`text-primary`,
151157
* `bg-primary`, `btn-primary`, `ring-primary-500`, …) ignores
152-
* `--devframe-primary` entirely only hand-written rules that already
158+
* `--devframe-primary` entirely; only hand-written rules that already
153159
* reference `--colors-primary-*` directly (the dock's glow gradient,
154160
* `primary-ramp.css` itself) retint. Swapping the baked triplet for `from
155161
* var(--colors-primary-<stop>, <hex>) r g b` keeps that exact alpha
156-
* mechanism intact while sourcing the base color from the variable a
162+
* mechanism intact while sourcing the base color from the variable, so a
157163
* rebrand's `--devframe-primary` now reaches every baked utility too.
158164
*
159165
* Call once per generated pass, after `generator.generate(...)`, passing the

0 commit comments

Comments
 (0)