@webjsdev/mcp 0.1.6 → 0.1.8

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 (33) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/resources/AGENTS.md +169 -77
  4. package/resources/SKILL.md +226 -0
  5. package/resources/references/auth-and-sessions.md +220 -0
  6. package/resources/references/built-ins.md +200 -0
  7. package/resources/references/client-router-and-streaming.md +204 -0
  8. package/resources/references/components.md +167 -0
  9. package/resources/references/data-and-actions.md +187 -0
  10. package/resources/references/muscle-memory-gotchas.md +170 -0
  11. package/resources/references/optimistic-ui.md +128 -0
  12. package/resources/references/routing-and-pages.md +158 -0
  13. package/resources/references/runtime.md +80 -0
  14. package/resources/references/service-worker.md +78 -0
  15. package/resources/references/styling.md +123 -0
  16. package/resources/references/testing.md +125 -0
  17. package/resources/references/typescript.md +148 -0
  18. package/src/mcp-docs.js +27 -14
  19. package/src/mcp-source.js +12 -2
  20. package/src/mcp.js +4 -3
  21. package/resources/agent-docs/advanced.md +0 -1251
  22. package/resources/agent-docs/built-ins.md +0 -379
  23. package/resources/agent-docs/components.md +0 -712
  24. package/resources/agent-docs/configuration.md +0 -284
  25. package/resources/agent-docs/framework-dev.md +0 -65
  26. package/resources/agent-docs/lit-muscle-memory-gotchas.md +0 -470
  27. package/resources/agent-docs/metadata.md +0 -334
  28. package/resources/agent-docs/recipes.md +0 -528
  29. package/resources/agent-docs/runtime.md +0 -65
  30. package/resources/agent-docs/service-worker.md +0 -100
  31. package/resources/agent-docs/styling.md +0 -279
  32. package/resources/agent-docs/testing.md +0 -438
  33. package/resources/agent-docs/typescript.md +0 -402
@@ -1,100 +0,0 @@
1
- # Service worker / offline primitive (opt-in, #271)
2
-
3
- webjs ships a hand-authored service worker and an offline fallback into the UI
4
- scaffolds (`public/sw.js`, `public/offline.html`; the full-stack and saas
5
- templates, since the api template has no UI). It adds an offline experience
6
- and an asset cache **without changing the JavaScript-disabled baseline**: the
7
- worker only ever registers from JavaScript, so with JS off no worker exists and
8
- pages, links, and forms behave exactly as before. It is **opt-in**: the files
9
- ship dormant and do nothing until the app registers the worker.
10
-
11
- This is a thin, hand-readable worker built directly on the native Service
12
- Worker and Cache Storage APIs. There is no Workbox, no precache framework, and
13
- no bundler step, matching webjs's no-build, close-to-web-standards posture.
14
-
15
- ## Enabling it (the opt-in registration snippet)
16
-
17
- Add this inline script to the root layout's `<head>` (`app/layout.{js,ts}`). It
18
- registers the worker after load, and only when JS is present, so it is
19
- progressive-enhancement-safe:
20
-
21
- ```html
22
- <script>
23
- if ('serviceWorker' in navigator) {
24
- addEventListener('load', () => {
25
- // Tie the worker version to the deploy: read the importmap build id and
26
- // register /sw.js?v=<build>, so a new deploy registers a "new" worker.
27
- const tag = document.querySelector('script[type="importmap"]');
28
- const build = (tag && tag.dataset.webjsBuild) || '';
29
- navigator.serviceWorker.register('/sw.js' + (build ? '?v=' + build : ''));
30
- });
31
- }
32
- </script>
33
- ```
34
-
35
- With webjs's CSP enabled (`webjs.csp` in package.json), stamp the nonce on the
36
- script. Read it in the layout with `import { cspNonce } from '@webjsdev/core'`
37
- and emit `<script nonce="${cspNonce()}">…`.
38
-
39
- ## What the worker does (scope + strategy)
40
-
41
- Registered from `/sw.js`, the worker's scope is the site root (`/`), so it sees
42
- every navigation and same-origin asset request.
43
-
44
- - **Navigations are network-first.** The worker always tries the network first,
45
- so the user sees fresh server-rendered HTML, and caches each successful page
46
- (the SSR shell). When the network fails, it serves the cached page if you have
47
- visited it, otherwise `/offline.html`. Network-first means the cache never
48
- makes a page go stale; it is purely an offline safety net.
49
- - **Static assets are stale-while-revalidate.** Same-origin modules (the per-file
50
- ESM the no-build runtime serves), the framework runtime under `/__webjs/core/`,
51
- vendor bundles under `/__webjs/vendor/`, and `public/` assets are served from
52
- cache when present and refreshed in the background. In production these URLs
53
- carry a `?v=<hash>` content fingerprint (#243), so a changed file gets a new
54
- URL and the cache can never serve stale bytes.
55
-
56
- **Never cached:** non-GET requests (writes), cross-origin requests, the action
57
- RPC endpoint (`/__webjs/action/`), and the dev-only `/__webjs/events` (SSE) and
58
- `/__webjs/reload.js`.
59
-
60
- ## Versioning ties to the deploy (`data-webjs-build`)
61
-
62
- The cache name is `webjs-<build>`, where `<build>` is the `?v=` query the
63
- registration passes (the importmap build id read from
64
- `data-webjs-build`). When a deploy changes the build id:
65
-
66
- 1. the page registers `/sw.js?v=<new-build>`, a different worker URL, so the
67
- browser fetches and installs the new worker;
68
- 2. the new worker's `activate` deletes every cache whose name is not the current
69
- `webjs-<new-build>`, evicting the prior deploy's cache.
70
-
71
- So a deploy refreshes the offline cache automatically, with no manual cache
72
- busting. Without a `?v=` (e.g. a dev registration), the cache name is
73
- `webjs-dev`.
74
-
75
- ## Updating the worker itself
76
-
77
- The browser re-checks `/sw.js` on navigation and replaces the worker when its
78
- bytes change. Because the registration URL carries the build id, a deploy always
79
- changes that URL and triggers the update. The worker calls `skipWaiting()` +
80
- `clients.claim()`, so a new version takes control promptly. To change the
81
- caching strategy, edit `public/sw.js`; it is your file, not a framework
82
- internal.
83
-
84
- ## Removing it
85
-
86
- Delete the registration snippet (and optionally `public/sw.js` /
87
- `public/offline.html`). To also un-register an already-installed worker on
88
- clients, ship a one-line `navigator.serviceWorker.getRegistrations().then(rs =>
89
- rs.forEach(r => r.unregister()))` for a release, or rely on the worker's own
90
- update lifecycle.
91
-
92
- ## Tests
93
-
94
- `test/service-worker/sw.test.mjs` runs the REAL `public/sw.js` source in a
95
- `node:vm` sandbox with mocked service-worker globals and drives its handlers:
96
- the install precache, the activate cache eviction, the network-first navigation
97
- (fresh + cached), the offline-cached + offline-fallback paths, and the
98
- never-cache rules (non-GET, cross-origin, the RPC endpoint), plus
99
- stale-while-revalidate. `test/scaffolds/scaffold-integration.test.js` asserts
100
- both files ship into a scaffolded app.
@@ -1,279 +0,0 @@
1
- # Styling: Tailwind-first, plus vanilla-CSS opt-out
2
-
3
- Tailwind is the strong default. The conventions below cover the
4
- Tailwind-first rule and the lit reflex it exists to counter, then how to
5
- opt out and use plain CSS everywhere (fully supported).
6
-
7
- ## Tailwind-first is the strong default
8
-
9
- **Use Tailwind utilities for pages AND light-DOM components (the default
10
- DOM mode).** Layout, spacing, color (via the `@theme` tokens),
11
- typography, borders, radius, shadows, and interaction states
12
- (hover/focus/active/disabled, dark mode) are all utility-expressible.
13
- Light DOM does not scope styles, so a utility class on a light-DOM
14
- element resolves against the global stylesheet exactly as it does on a
15
- page. That is why utilities are the right tool there, not an exception.
16
-
17
- ### The lit muscle-memory trap (read this first)
18
-
19
- AI agents with strong lit / web-components training carry one habit that
20
- fights this default: in lit, a component owns a shadow root and scopes
21
- its CSS with `static styles = css\`\``, so the reflex is to author scoped
22
- CSS or an inline `<style>` with semantic class names (`.hero`,
23
- `.feature`, `.card`, `.btn`) for every component.
24
-
25
- **In webjs the default is light DOM, which does NOT scope.** A scoped
26
- `css` block does nothing without `static shadow = true` (the framework
27
- warns at runtime), and an inline `<style>` with bare semantic class names
28
- leaks those names into the global namespace. So reaching for either in a
29
- light-DOM component is the reflex to resist. Prefer Tailwind utilities.
30
- When the same utility bundle repeats, extract it into a `lib/utils/ui.ts`
31
- helper that returns an `` html`...` `` fragment (the existing pattern,
32
- below), NOT a CSS class. The helper keeps the utilities visible at the
33
- definition site and runs at SSR time, so the output is identical to
34
- writing the classes inline.
35
-
36
- ### The custom-CSS allowlist (the only things raw CSS is for)
37
-
38
- Reserve raw CSS for what utilities genuinely cannot express. This is the
39
- exhaustive list; anything outside it should be a utility (or a
40
- `lib/utils/ui.ts` helper):
41
-
42
- - **design-token `:root` + `@theme` definitions** (the palette, fonts,
43
- fluid type scale, motion durations declared once in the root layout),
44
- - **`@property` animated custom properties** paired with `@keyframes`,
45
- - **`::-webkit-scrollbar` and `scrollbar-color`** (no utility surface),
46
- - **`prefers-reduced-motion` blocks**,
47
- - **complex `color-mix()` or gradient effects** a utility cannot spell.
48
-
49
- When custom CSS IS unavoidable inside a light-DOM component, the
50
- tag-prefix invariant still holds (see the Vanilla CSS section below):
51
- every class selector is prefixed with the component tag. **Shadow-DOM components
52
- (`static shadow = true`) legitimately author `static styles = css\`\``,
53
- which is the right home for scoped CSS and is unchanged by this rule.**
54
- The Tailwind-first steer is about the LIGHT-DOM default, not about
55
- shadow DOM.
56
-
57
- ## Tailwind + JS helpers (default convention)
58
-
59
- Default stack: Tailwind CSS browser runtime + `@theme` design tokens
60
- declared once in the root layout (palette, fonts, fluid type, motion
61
- durations). Consume via utility classes (`text-fg`, `bg-bg-elev`,
62
- `font-serif`, `duration-fast`, `text-display`).
63
-
64
- **DRY via JS helpers, not `@apply`.** When the same bundle of Tailwind
65
- classes repeats across 2+ places, extract it into a helper in
66
- `lib/utils/ui.ts`:
67
-
68
- ```ts
69
- import { html } from '@webjsdev/core';
70
-
71
- /** `● label` kicker: small caps, accent colour, above headings. */
72
- export function rubric(label: string, mb: 'sm' | 'md' = 'md') {
73
- const mbCls = mb === 'sm' ? 'mb-3' : 'mb-4';
74
- return html`
75
- <span class="block font-mono text-[11px] leading-none font-semibold tracking-[0.2em] uppercase text-accent ${mbCls}">● ${label}</span>
76
- `;
77
- }
78
-
79
- /** "← label" back link: small caps, muted. */
80
- export function backLink(href: string, label: string) {
81
- return html`
82
- <a href=${href} class="inline-block mb-12 text-fg-subtle no-underline font-mono text-[11px] leading-none font-medium tracking-[0.15em] uppercase transition-colors duration-fast hover:text-fg">← ${label}</a>
83
- `;
84
- }
85
- ```
86
-
87
- ```ts
88
- // app/blog/[slug]/page.ts
89
- import { rubric, backLink } from '../../../lib/utils/ui.ts';
90
-
91
- export default function Post({ params }) {
92
- return html`
93
- ${backLink('/', 'Posts')}
94
- ${rubric('post')}
95
- <h1 class="font-serif text-display ...">${title}</h1>
96
- `;
97
- }
98
- ```
99
-
100
- ### When to extract, when to keep inline
101
-
102
- | Repeats | Action |
103
- |---|---|
104
- | Once | Inline the classes. |
105
- | 2–3 times, identical | Extract to `lib/utils/ui.ts`. |
106
- | Varies by 1–2 props | Extract with a small parameter (`mb: 'sm' \| 'md'`). |
107
- | Radically different per call site | Keep inline. Don't force-fit. |
108
-
109
- ### Why not `@apply`?
110
-
111
- `@apply` hides which utilities a class uses from the reader and creates
112
- a second source of truth. JS helpers keep the class bundle visible at
113
- the definition site and compose naturally with other props (conditional
114
- classes, active states, etc.). They run at SSR time. Output HTML is
115
- identical to inline classes, no client-side runtime.
116
-
117
- ## Dark mode: two signals, keep them in sync
118
-
119
- The default scaffold runs **two** theming systems, and a theme switch must
120
- drive **both** or one half goes stale (this is the single most common
121
- dark-mode bug in a scaffolded app):
122
-
123
- 1. **Editorial chrome tokens** (`--fg`, `--bg`, `--accent`, ...) declared in
124
- the root layout. They react to a **`data-theme` attribute** on `<html>`
125
- (`data-theme="light"` vs absent) and default to dark.
126
- 2. **Webjs UI (shadcn) component tokens** (`--background`, `--foreground`,
127
- `--primary`, ...) used by everything under `components/ui/`. They react
128
- to a **`.dark` class** on an ancestor (`@custom-variant dark (&:is(.dark *))`)
129
- and default to light.
130
-
131
- The scaffold's head init script and `theme-toggle` set **both** signals on
132
- `<html>`: they write `data-theme` AND `classList.toggle('dark', isDark)`. If
133
- you wire your own theme switch or replace the toggle, you MUST set both.
134
- Setting only `data-theme` leaves the ui-* components rendering light tokens
135
- on a dark page (white buttons, white cards, invisible text) while the chrome
136
- looks correct.
137
-
138
- **Verify dark mode in a real browser, not just light.** Light mode passing
139
- proves nothing about dark mode: with neither signal set, both systems sit at
140
- a coincidentally matching default, so the divergence only appears once
141
- `.dark` / `data-theme` are applied. Emulate dark (`colorScheme: 'dark'` or
142
- flip the toggle) and check a shadcn component's computed `background-color`,
143
- not just the page chrome.
144
-
145
- ## Vanilla CSS for the whole app (opt-out of Tailwind)
146
-
147
- Tailwind isn't required. To hand-write CSS everywhere, you need a
148
- scoping convention so generic class names (`.btn`, `.input`, `.header`)
149
- don't collide across pages, layouts, and components in the global
150
- light-DOM namespace.
151
-
152
- ### Convention: three scopes, one rule each
153
-
154
- | Scope | Wrapper selector | Where it lives |
155
- |---|---|---|
156
- | **Component** | Custom-element tag | Nested CSS under `my-counter { … }` |
157
- | **Page** | `.page-<route>` | Wrap the page's markup in `<div class="page-<route>">` |
158
- | **Layout** | `.layout-<name>` | Wrap the layout's markup in `<div class="layout-<name>">` |
159
-
160
- Naming convention: derive the scope class from the file path. Slashes
161
- → hyphens. Dynamic segments become their param name. Route groups
162
- `(marketing)` drop.
163
-
164
- - `app/page.ts` → `.page-home`
165
- - `app/about/page.ts` → `.page-about`
166
- - `app/dashboard/posts/new/page.ts` → `.page-dashboard-posts-new`
167
- - `app/blog/[slug]/page.ts` → `.page-blog-slug`
168
- - `app/(marketing)/about/page.ts` → `.page-about`
169
- - `app/layout.ts` → `.layout-root`
170
- - `app/admin/layout.ts` → `.layout-admin`
171
-
172
- Styles colocate with the markup as `const STYLES = css\`…\`` and
173
- interpolate via `<style>${STYLES.text}</style>`. The standalone
174
- `@webjsdev/intellisense` (and the `webjs` editor extension) resolves class
175
- go-to-definition inside those blocks.
176
-
177
- ### Example: a page
178
-
179
- ```ts
180
- import { html, css } from '@webjsdev/core';
181
-
182
- const STYLES = css`
183
- .page-dashboard {
184
- .actions { display: flex; gap: 12px; }
185
- .btn { padding: 12px 24px; border-radius: 999px; }
186
- .btn-primary { background: var(--accent); color: var(--accent-fg); }
187
- }
188
- `;
189
-
190
- export default function Dashboard() {
191
- return html`
192
- <style>${STYLES.text}</style>
193
- <div class="page-dashboard">
194
- <div class="actions">
195
- <a class="btn btn-primary" href="/new">+ New</a>
196
- </div>
197
- </div>
198
- `;
199
- }
200
- ```
201
-
202
- ### Example: a layout
203
-
204
- ```ts
205
- const STYLES = css`
206
- .layout-root {
207
- .header { position: sticky; top: 0; }
208
- .nav { display: flex; gap: 16px; }
209
- }
210
- `;
211
-
212
- export default function RootLayout({ children }) {
213
- return html`
214
- <style>${STYLES.text}</style>
215
- <div class="layout-root">
216
- <header class="header">
217
- <nav class="nav">…</nav>
218
- </header>
219
- <main>${children}</main>
220
- </div>
221
- `;
222
- }
223
- ```
224
-
225
- Inside each scope, `.btn` / `.input` / `.header` / `.form` / `.item`
226
- are free names because CSS descendant combinators stop them at the scope
227
- boundary. A small curated set of **primitives** (`rubric`, `banner`,
228
- `accent-link`, `display-h1`, …) can live global in the root layout as
229
- your design system. Everything else is scoped.
230
-
231
- ### Tradeoffs vs Tailwind
232
-
233
- More files you write, more discipline required, slight rename cost (2
234
- textual edits when a route folder moves). In exchange: no browser-
235
- runtime script, no `@theme` block, idiomatic CSS, plain cascade you can
236
- debug with any tool.
237
-
238
- ## Gotcha: a sticky header flickers on iOS, use `position: fixed`
239
-
240
- A `position: sticky` header (the common `sticky top-0` pattern) flickers its
241
- background for one frame on iOS WebKit (every iOS browser, since they all use
242
- WebKit) during a client-router FORWARD navigation. The scroll-to-top the router
243
- runs after the content swap drives a sticky stuck-to-static recompute that
244
- WebKit mis-repaints. It is iOS-only (fine on desktop and Android, and invisible
245
- in DevTools device emulation, so it shows only on a real device). #610 confirmed
246
- on-device that neither compositor promotion (`translateZ(0)` / `will-change`)
247
- nor changing the swap paint timing fixes it.
248
-
249
- The fix is `position: fixed` instead of sticky. A fixed header is always pinned
250
- and never does the scroll-relative recompute, so the repaint bug never fires.
251
- There is no reliable CSS-only fix that KEEPS `position: sticky` (the GPU-promotion
252
- hacks `translateZ(0)` / `translate3d` / `will-change` / a wrapper element are all
253
- variants that do NOT reliably work, confirmed on-device for #610). Preserving the
254
- header across nav is correct and standard (Next.js, Remix, SvelteKit all preserve
255
- nested layouts); only the `sticky` positioning is the problem.
256
-
257
- Because fixed leaves normal flow, reserve the header height on the content below.
258
- Use a single `--header-height` custom property as the source of truth rather than
259
- a hardcoded number, and keep it exact with a `ResizeObserver` so it tracks
260
- responsive / dynamic headers (degrades fine with no JS via the default):
261
-
262
- ```css
263
- :root { --header-h: 56px; } /* sane SSR first-paint default */
264
- header { position: fixed; inset-inline: 0; top: 0; }
265
- body { padding-top: var(--header-h); }
266
- ```
267
- ```js
268
- // refine the default to the header's real height (and on resize)
269
- const hdr = document.querySelector('header');
270
- const apply = () => document.documentElement.style.setProperty('--header-h', hdr.offsetHeight + 'px');
271
- apply();
272
- new ResizeObserver(apply).observe(hdr);
273
- ```
274
-
275
- The example blog's root layout does exactly this (`examples/blog/app/layout.ts`).
276
- For an app/dashboard layout, an alternative is an app-shell scroll container (a
277
- non-scrolling `100dvh` flex column with `<main>` as the internal scroller), which
278
- needs no offset at all but changes the scroll model (the window no longer
279
- scrolls, so the iOS URL bar stays visible).