@webjsdev/cli 0.10.55 → 0.10.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/lib/create.js +35 -0
  2. package/lib/doctor/codes.js +66 -0
  3. package/lib/doctor/manifest.js +161 -0
  4. package/lib/doctor/policy.js +124 -0
  5. package/lib/doctor/probes/elision.js +111 -0
  6. package/lib/doctor/probes/env.js +53 -0
  7. package/lib/doctor/probes/framework-resolves.js +84 -0
  8. package/lib/doctor/probes/git-hook.js +58 -0
  9. package/lib/doctor/probes/importmap-coherence.js +158 -0
  10. package/lib/doctor/probes/node.js +37 -0
  11. package/lib/doctor/probes/static-asset-freshness.js +58 -0
  12. package/lib/doctor/probes/tsconfig.js +55 -0
  13. package/lib/doctor/probes/unmarked-asset-links.js +199 -0
  14. package/lib/doctor/probes/vendor-gitignore.js +84 -0
  15. package/lib/doctor/probes/vendor-pin.js +77 -0
  16. package/lib/doctor/probes/webjs-versions.js +85 -0
  17. package/lib/doctor/route-modules.js +100 -0
  18. package/lib/doctor/runner.js +71 -0
  19. package/lib/doctor/util.js +160 -0
  20. package/lib/doctor.js +5 -1634
  21. package/package.json +1 -1
  22. package/templates/.agents/skills/webjs/SKILL.md +41 -2
  23. package/templates/.agents/skills/webjs/references/client-router-and-streaming.md +20 -2
  24. package/templates/.agents/skills/webjs/references/components.md +41 -1
  25. package/templates/.agents/skills/webjs/references/module-structure.md +229 -0
  26. package/templates/.agents/skills/webjs/references/muscle-memory-gotchas.md +1 -1
  27. package/templates/.agents/skills/webjs/references/routing-and-pages.md +36 -2
  28. package/templates/.agents/skills/webjs/references/runtime.md +5 -0
  29. package/templates/gallery/app/features/boundaries/page.ts +11 -0
  30. package/templates/gallery/app/features/client-router/page.ts +8 -1
  31. package/templates/gallery/modules/client-router/components/router-controls.ts +16 -2
  32. package/templates/gallery/modules/gallery/nav.ts +35 -26
@@ -3,6 +3,15 @@
3
3
  * left sidebar (so they can never drift). A browser-safe data module (no server
4
4
  * imports, no client globals): the home flattens the groups into its card grid,
5
5
  * and <gallery-nav> renders them grouped. gallery:clear removes this module.
6
+ *
7
+ * Blurbs are INTENT-shaped, not noun-shaped: each one opens on the job you would
8
+ * be doing when you want this demo, because the index is the first thing an
9
+ * agent reads and a card that only names the feature cannot be found by someone
10
+ * who does not know the feature exists. The skill's cheat sheet
11
+ * (.agents/skills/webjs/SKILL.md, "Reach For The Right Primitive") carries the
12
+ * same set keyed the same way, and the repo's
13
+ * test/repo-health/skill-gallery-intent-parity.test.mjs fails if the two fall
14
+ * out of step.
6
15
  */
7
16
  export interface NavItem { href: string; title: string; blurb: string; }
8
17
  export interface NavGroup { label: string; items: NavItem[]; }
@@ -11,68 +20,68 @@ export const FEATURE_GROUPS: NavGroup[] = [
11
20
  {
12
21
  label: 'Routing',
13
22
  items: [
14
- { href: '/features/routing', title: 'Routing', blurb: 'A static route plus a dynamic [id] segment that reads params. The file-based router in miniature.' },
15
- { href: '/features/boundaries', title: 'Boundaries', blurb: 'The control-flow throws (forbidden / unauthorized / notFound) and the nearest boundary file that catches each.' },
16
- { href: '/features/metadata', title: 'Metadata', blurb: 'Static metadata plus generateMetadata(ctx), which reads the request to compute the title and Open Graph tags.' },
23
+ { href: '/features/routing', title: 'Routing', blurb: 'Add a URL to the app, static or with a dynamic [id] segment. The file is the route, so there is no table to register it in.' },
24
+ { href: '/features/boundaries', title: 'Boundaries', blurb: 'Abandon a render because something is missing or not allowed. Throw notFound / forbidden / unauthorized and the nearest boundary file catches it.' },
25
+ { href: '/features/metadata', title: 'Metadata', blurb: 'Give a page its own title, description, and social preview without writing head markup yourself.' },
17
26
  ],
18
27
  },
19
28
  {
20
29
  label: 'Components',
21
30
  items: [
22
- { href: '/features/components', title: 'Components', blurb: 'The WebComponent factory, reactive props, instance signals, and slot projection in light DOM.' },
23
- { href: '/features/directives', title: 'Directives', blurb: 'The lit-html directive set: repeat for keyed lists, watch(signal) for a fine-grained node swap.' },
24
- { href: '/features/async-render', title: 'Async render', blurb: 'A component that awaits server data in async render(), so the resolved value is in the first paint.' },
31
+ { href: '/features/components', title: 'Components', blurb: 'Make one part of the page respond to a click or hold state. A page never hydrates, so interactivity lives in a component.' },
32
+ { href: '/features/directives', title: 'Directives', blurb: 'Render a keyed list, or swap a single node when state changes, without re-rendering the component around it.' },
33
+ { href: '/features/async-render', title: 'Async render', blurb: 'Get server data into the first paint. Await it in async render() rather than fetching after mount, which SSR never runs.' },
25
34
  ],
26
35
  },
27
36
  {
28
37
  label: 'Data & actions',
29
38
  items: [
30
- { href: '/features/server-actions', title: 'Server actions', blurb: 'A use-server RPC action next to a server-only .server.ts utility, plus the HTTP-verb config exports that make a read a cached GET.' },
31
- { href: '/features/route-handler', title: 'Route handlers', blurb: 'A server-only route.ts HTTP endpoint returning JSON, the WebJs equivalent of a Next route handler.' },
32
- { href: '/features/forms', title: 'Forms', blurb: 'A no-JS progressive-enhancement form bound to a server action, with server-side validation errors.' },
33
- { href: '/features/optimistic-ui', title: 'Optimistic UI', blurb: 'The imperative optimistic(signal, value, action) flip: instant update, automatic rollback on failure.' },
39
+ { href: '/features/server-actions', title: 'Server actions', blurb: 'Call server code from the browser by importing the function. No fetch, no endpoint to name, and the types survive the trip.' },
40
+ { href: '/features/route-handler', title: 'Route handlers', blurb: 'Expose JSON to a caller outside the app. A server-only route.ts, the WebJs equivalent of a Next route handler.' },
41
+ { href: '/features/forms', title: 'Forms', blurb: 'Write data from a form that still works with JS off. Binding the action to the form is the whole wiring.' },
42
+ { href: '/features/optimistic-ui', title: 'Optimistic UI', blurb: 'Make a mutation feel instant. optimistic() applies the change immediately and rolls it back if the server refuses.' },
34
43
  ],
35
44
  },
36
45
  {
37
46
  label: 'Client & streaming',
38
47
  items: [
39
- { href: '/features/client-router', title: 'Client router', blurb: 'Automatic soft navigation: fragment-only fetches, hover prefetch, scroll restore, and graceful no-JS fallback.' },
40
- { href: '/features/view-transitions', title: 'View transitions', blurb: 'The opt-in view-transition meta cross-fades a soft navigation, with a data-webjs-permanent element persisted across the swap.' },
41
- { href: '/features/streaming', title: 'Streaming actions', blurb: 'A use-server action that returns an async generator, streamed to the call site token by token with for await.' },
42
- { href: '/features/stream', title: 'Stream updates', blurb: 'The <webjs-stream> element: renderStream() applies surgical append / replace / remove DOM updates by target id, no region redraw.' },
43
- { href: '/features/suspense', title: 'Suspense boundary', blurb: 'The <webjs-suspense> element: a first-paint fallback for a SLOW component, with the resolved content streamed in.' },
44
- { href: '/features/frames', title: 'Frames', blurb: 'A webjs-frame region that swaps a filtered sub-list in place from a link, shipping zero component JS, with a no-JS full-nav fallback.' },
48
+ { href: '/features/client-router', title: 'Client router', blurb: 'Navigate without a full page reload. Automatic the moment a page ships a component, with nothing to import or configure.' },
49
+ { href: '/features/view-transitions', title: 'View transitions', blurb: 'Cross-fade a navigation instead of snapping. One opt-in meta, plus a marker for elements that must survive the swap.' },
50
+ { href: '/features/streaming', title: 'Streaming actions', blurb: 'Show tokens or progress as the server produces them. An action returning an async generator, consumed with for await.' },
51
+ { href: '/features/stream', title: 'Stream updates', blurb: 'Change one element after a write, like appending a row or bumping a count, without redrawing the region around it.' },
52
+ { href: '/features/suspense', title: 'Suspense boundary', blurb: 'Paint the page before a slow region is ready. The fallback flushes on the first byte and the content streams in behind it.' },
53
+ { href: '/features/frames', title: 'Frames', blurb: 'Refresh one region on its own with no navigation, like a filtered list or a tab panel. Zero component JS, full-nav fallback with JS off.' },
45
54
  ],
46
55
  },
47
56
  {
48
57
  label: 'Real-time',
49
58
  items: [
50
- { href: '/features/websockets', title: 'WebSockets', blurb: 'A WS(ws, req) route endpoint plus the connectWS() client, echoing messages over a live socket.' },
51
- { href: '/features/broadcast', title: 'Broadcast', blurb: 'Fan a message out to every connected client on a WebSocket path, so all open tabs stay in sync.' },
59
+ { href: '/features/websockets', title: 'WebSockets', blurb: 'Hold a live two-way connection instead of polling. A WS(ws, req) route export on the server, connectWS() on the client.' },
60
+ { href: '/features/broadcast', title: 'Broadcast', blurb: 'Push one update to every client connected on a WebSocket path, so a change made in one of them shows up in the rest. Optionally excluding the sender.' },
52
61
  ],
53
62
  },
54
63
  {
55
64
  label: 'Auth & sessions',
56
65
  items: [
57
- { href: '/features/auth', title: 'Auth', blurb: 'Password login on createAuth, a signed session cookie, and a real protected route that redirects anonymous visitors to login.' },
58
- { href: '/features/sessions', title: 'Sessions', blurb: 'A signed-cookie session applied by a segment middleware, read and written per visitor with getSession() in a route.' },
66
+ { href: '/features/auth', title: 'Auth', blurb: 'Add login and a route only signed-in visitors can open, without rolling password hashing and session cookies yourself.' },
67
+ { href: '/features/sessions', title: 'Sessions', blurb: 'Remember something per visitor across requests. A signed cookie applied by middleware, read and written with getSession().' },
59
68
  ],
60
69
  },
61
70
  {
62
71
  label: 'Built-ins',
63
72
  items: [
64
- { href: '/features/caching', title: 'Caching', blurb: 'export const revalidate caches the page HTML per URL, with the safety rule for when a shared cache is allowed.' },
65
- { href: '/features/env', title: 'Env vars', blurb: 'The server-only vs WEBJS_PUBLIC_ boundary, read during SSR so secrets never reach the browser.' },
66
- { href: '/features/rate-limit', title: 'Rate limiting', blurb: 'The rateLimit() middleware scoped to one endpoint, returning a 429 with Retry-After once the interval resets.' },
67
- { href: '/features/file-storage', title: 'File storage', blurb: 'A no-JS multipart upload streamed into the FileStore, then served back through a streaming route.' },
68
- { href: '/features/service-worker', title: 'Service worker', blurb: 'The opt-in offline enhancement, registered from a browser-only lifecycle hook (never a page or layout).' },
73
+ { href: '/features/caching', title: 'Caching', blurb: 'Stop re-rendering a page that is identical for every visitor. export const revalidate, with the rule for when that is safe.' },
74
+ { href: '/features/env', title: 'Env vars', blurb: 'Read config and secrets at runtime while keeping the secrets server-side. WEBJS_PUBLIC_ is the only prefix the browser sees.' },
75
+ { href: '/features/rate-limit', title: 'Rate limiting', blurb: 'Stop one caller hammering an endpoint. The rateLimit() middleware returns a 429 with Retry-After until the interval resets.' },
76
+ { href: '/features/file-storage', title: 'File storage', blurb: 'Accept an upload and serve it back, streamed both ways, with nothing buffered in memory and nothing written into public/.' },
77
+ { href: '/features/service-worker', title: 'Service worker', blurb: 'Keep the app usable offline. The opt-in service worker, registered from a browser-only lifecycle hook (never a page or layout).' },
69
78
  ],
70
79
  },
71
80
  ];
72
81
 
73
82
  /** The whole example apps (composed features), shown after the single-feature demos. */
74
83
  export const EXAMPLES: NavItem[] = [
75
- { href: '/examples/todo', title: 'Optimistic todo', blurb: 'A whole app composing several features: the declarative optimistic() list API, progressive-enhancement forms, accessible labels, the modules split, and SQLite.' },
84
+ { href: '/examples/todo', title: 'Optimistic todo', blurb: 'See the pieces composed in one real feature: the declarative optimistic() list API, progressive-enhancement forms, accessible labels, the modules split, and SQLite.' },
76
85
  ];
77
86
 
78
87
  /**