Skip to content

Commit 00487b5

Browse files
committed
fix(website): restore the claims the trim broke and cover the demo grid
The previous commit traded accuracy for grid height and lost. Trimming the what-is-webjs card cut the two phrases that were carrying it: without 'with JSDoc in node_modules' the framework claim is flatly false, since core resolves its default entry to dist/webjs-core.js, and without 'read from one source' the AGENTS.md sentence claims five tools read a file three of them do not. Both phrases are back, and the length is won by dropping a convention detail instead. The demo grid was also entirely unasserted, so it could have been deleted with every test in the file still green. It now pins both panels, their correspondence, and the accessible name each block carries, plus a snippet-window assertion mirroring the one on the sibling page. The role=region fix goes site-wide rather than stopping at one page: the prohibited-name construct was on ten more blocks. The one block that is not focusable loses its name instead of gaining a role, since nothing can focus it to hear one.
1 parent b57b139 commit 00487b5

4 files changed

Lines changed: 38 additions & 9 deletions

File tree

website/app/page.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ function codeWindow(title: string, sample: string) {
103103
return html`
104104
<figure class=${WIN}>
105105
<figcaption class=${WINBAR}>${DOTS}<span class=${WINNAME}>${title}</span></figcaption>
106-
<pre class="scroll-thin m-0 p-4 overflow-x-auto font-mono text-sm leading-[1.7] [tab-size:2] flex-1" tabindex="0" aria-label=${title + ' code sample'}><code>${highlight(sample)}</code></pre>
106+
<pre class="scroll-thin m-0 p-4 overflow-x-auto font-mono text-sm leading-[1.7] [tab-size:2] flex-1" role="region" tabindex="0" aria-label=${title + ' code sample'}><code>${highlight(sample)}</code></pre>
107107
</figure>
108108
`;
109109
}
@@ -216,7 +216,7 @@ export default function LandingPage() {
216216
<div class="hero-stage max-w-5xl mx-auto grid grid-cols-1 wide:grid-cols-2 rounded-2xl overflow-hidden border border-border-strong bg-bg-sunken shadow-[var(--shadow)]">
217217
<div class="min-w-0 border-b wide:border-b-0 wide:border-r border-border bg-bg-subtle">
218218
<div class=${WINBAR}>${DOTS}<span class=${WINNAME}>components/like-button.ts</span></div>
219-
<pre class="scroll-thin m-0 p-5 overflow-x-auto font-mono text-xs leading-[1.72] [tab-size:2] text-left" tabindex="0" aria-label="like-button component source"><code>${highlight(HERO_SAMPLE)}</code></pre>
219+
<pre class="scroll-thin m-0 p-5 overflow-x-auto font-mono text-xs leading-[1.72] [tab-size:2] text-left" role="region" tabindex="0" aria-label="like-button component source"><code>${highlight(HERO_SAMPLE)}</code></pre>
220220
</div>
221221
<div class="group/stage flex flex-col min-w-0 bg-bg">
222222
<input type="checkbox" id="stage-usage" class="sr-only peer" />
@@ -232,7 +232,7 @@ export default function LandingPage() {
232232
<div class="flex-1 grid place-items-center px-6 py-10 group-has-[:checked]/stage:hidden">
233233
<like-button count="3"></like-button>
234234
</div>
235-
<pre class="hidden group-has-[:checked]/stage:block flex-1 m-0 p-5 overflow-x-auto font-mono text-xs leading-[1.72] text-left" aria-label="like-button usage"><code>${highlight(USAGE_SAMPLE)}</code></pre>
235+
<pre class="hidden group-has-[:checked]/stage:block flex-1 m-0 p-5 overflow-x-auto font-mono text-xs leading-[1.72] text-left"><code>${highlight(USAGE_SAMPLE)}</code></pre>
236236
<div class="px-4 py-3 border-t border-border text-center font-mono text-xs leading-[1.5] text-fg-subtle">
237237
Server-rendered first, then upgraded. Click it.
238238
</div>
@@ -384,7 +384,7 @@ export default function LandingPage() {
384384
<span class="text-xs font-medium leading-none text-accent">Full-stack</span>
385385
<h3 class="font-display font-bold text-lg leading-[1.25] m-0">Pages + API + components</h3>
386386
<p class="m-0 text-sm leading-[1.6] text-fg-muted">SSR pages, web components, server actions, Drizzle, streaming, and a browsable feature gallery. Auth (login, sessions, a protected route) ships as a gallery card. The default.</p>
387-
<pre class="scroll-thin m-0 px-3.5 py-3 overflow-x-auto rounded-lg border border-border bg-bg-subtle font-mono text-xs leading-[1.6] text-fg-muted" tabindex="0" aria-label="Example files">app/page.ts
387+
<pre class="scroll-thin m-0 px-3.5 py-3 overflow-x-auto rounded-lg border border-border bg-bg-subtle font-mono text-xs leading-[1.6] text-fg-muted" role="region" tabindex="0" aria-label="Example files">app/page.ts
388388
components/counter.ts
389389
actions/posts.server.ts</pre>
390390
<div class="cmd-foot pt-2 mt-auto font-mono text-xs leading-[1.6] text-fg-muted max-w-full min-w-0"><copy-cmd>npm create webjs@latest my-app</copy-cmd></div>
@@ -393,7 +393,7 @@ actions/posts.server.ts</pre>
393393
<span class="text-xs font-medium leading-none text-accent">Backend (API)</span>
394394
<h3 class="font-display font-bold text-lg leading-[1.25] m-0">Route handlers + Database</h3>
395395
<p class="m-0 text-sm leading-[1.6] text-fg-muted">A backend-only app, no UI or SSR. File-based route handlers, modules, middleware, rate limiting, WebSockets, a database, and a backend-features gallery.</p>
396-
<pre class="scroll-thin m-0 px-3.5 py-3 overflow-x-auto rounded-lg border border-border bg-bg-subtle font-mono text-xs leading-[1.6] text-fg-muted" tabindex="0" aria-label="Example files">app/api/users/route.ts
396+
<pre class="scroll-thin m-0 px-3.5 py-3 overflow-x-auto rounded-lg border border-border bg-bg-subtle font-mono text-xs leading-[1.6] text-fg-muted" role="region" tabindex="0" aria-label="Example files">app/api/users/route.ts
397397
app/api/chat/route.ts
398398
middleware.ts</pre>
399399
<div class="cmd-foot pt-2 mt-auto font-mono text-xs leading-[1.6] text-fg-muted max-w-full min-w-0"><copy-cmd>npm create webjs@latest my-api -- --template api</copy-cmd></div>

website/app/what-is-webjs/page.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ const PROSE = 'text-fg-muted text-base leading-[1.7] m-0';
161161
const CAPABILITIES = [
162162
{
163163
title: 'AI-first, readable end to end',
164-
body: 'Predictable file conventions, one server function per file, and an explicit .server.ts boundary keep the context small, so an agent edits one route without reading the whole app, and a request written in ordinary words still lands in the right shape. Every app ships one AGENTS.md contract that Claude Code, Cursor, Copilot, Gemini, and opencode all read, and the framework is plain JavaScript rather than a compiled bundle.',
164+
body: 'Predictable file conventions and an explicit .server.ts boundary decide the shape of an app, so an agent edits one route without reading the whole app, and ordinary words are enough to ask for a new one. Every app ships an AGENTS.md contract that Claude Code, Cursor, Copilot, Gemini, and opencode read from one source, and the framework itself is plain JavaScript with JSDoc in node_modules rather than a compiled bundle.',
165165
},
166166
{
167167
title: 'Web components, server-rendered',
@@ -207,7 +207,7 @@ function codeWindow(title: string, sample: string, label: string) {
207207
return html`
208208
<figure class=${WIN}>
209209
<figcaption class=${WINBAR}>${DOTS}<span class=${WINNAME}>${title}</span></figcaption>
210-
<pre class="scroll-thin m-0 p-4 overflow-x-auto font-mono text-sm leading-[1.7] [tab-size:2] flex-1" tabindex="0" aria-label=${label}><code>${highlight(sample)}</code></pre>
210+
<pre class="scroll-thin m-0 p-4 overflow-x-auto font-mono text-sm leading-[1.7] [tab-size:2] flex-1" role="region" tabindex="0" aria-label=${label}><code>${highlight(sample)}</code></pre>
211211
</figure>
212212
`;
213213
}

website/app/why-webjs/page.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export function generateMetadata(ctx: { url: string }) {
2424
const image = `${origin}/public/og-why.png`;
2525
const title = 'Why WebJs - The Framework Your AI Agent Already Understands';
2626
const description =
27-
'WebJs is a full-stack JavaScript framework you can describe in plain language, because the file conventions land an app in the right shape whatever model you use. Nothing is hidden behind a build step, so any AI model reads the framework source from node_modules, reasons about the whole stack, and debugs it. No training data required, no single blessed model, on the web components and HTML every model already knows.';
27+
'WebJs is a full-stack JavaScript framework you describe in plain language, because the file conventions land an app in the right shape whatever model you use. Nothing is hidden behind a build step, so any AI model reads the framework source from node_modules, reasons about the whole stack, and debugs it. No training data required, no single blessed model, on the web components and HTML every model already knows.';
2828
return {
2929
title,
3030
description,
@@ -200,7 +200,7 @@ Counter.register('counter');
200200
<pre class="scroll-thin m-0 p-4 overflow-x-auto font-mono text-sm leading-[1.7] [tab-size:2] flex-1" role="region" tabindex="0" aria-label="The files a WebJs app grows for that request"><code>app/book/page.ts
201201
<span class="text-fg-subtle"> the page, server-rendered to real HTML</span>
202202
app/staff/bookings/page.ts
203-
<span class="text-fg-subtle"> the list, behind the session check</span>
203+
<span class="text-fg-subtle"> the staff view, its own route</span>
204204
modules/bookings/actions/create.server.ts
205205
<span class="text-fg-subtle"> the write, server-side by extension</span>
206206
db/schema.server.ts

website/test/ssr/why-webjs-ssr.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,35 @@ test('the pitch page makes the plain-language prompt and cross-model quality arg
3535
assert.ok(out.includes('quality of what comes back'), 'ties model-agnosticism to output quality, not just to whether a model works');
3636
});
3737

38+
test('the plain-language section pairs a prompt against the files the conventions produce', async () => {
39+
// The prose above the demo carries the argument, but the two windows are what
40+
// SHOW it, and they were previously unasserted: the whole grid could be
41+
// deleted with every test in this file still green. These pin both panels,
42+
// their correspondence, and the accessible name each scrollable block needs.
43+
const out = await renderToString(Why());
44+
assert.ok(out.includes('Let customers book a table'), 'the prompt panel carries a request written in ordinary words');
45+
assert.ok(!/&gt; Build a page/.test(out), 'the prompt does not name a page, which the file panel would then have to match one for one');
46+
for (const file of ['app/book/page.ts', 'app/staff/bookings/page.ts', 'modules/bookings/actions/create.server.ts', 'db/schema.server.ts']) {
47+
assert.ok(out.includes(file), `the file panel answers the prompt with ${file}`);
48+
}
49+
assert.ok(out.includes('<span class="ml-2 font-mono font-medium text-xs leading-none text-fg-subtle">files</span>'), 'the file listing is labelled files, not terminal');
50+
// <pre> maps to role generic, where ARIA prohibits an author-supplied name,
51+
// so every named scrollable block on this page carries an explicit role.
52+
const named = out.match(/<pre[^>]*aria-label=/g) ?? [];
53+
const region = out.match(/<pre[^>]*role="region"[^>]*aria-label=/g) ?? [];
54+
assert.equal(named.length, 4, 'the page renders its four named code blocks');
55+
assert.equal(region.length, named.length, 'every named pre carries role=region, so the name is one ARIA permits');
56+
});
57+
58+
test('the /why-webjs description answers in the snippet window a SERP actually shows', async () => {
59+
// Mirrors the assertion on /what-is-webjs. The claim this page leads with is
60+
// only worth adding if it survives truncation, and nothing pinned that here.
61+
const { description } = generateMetadata({ url: 'https://webjs.dev/why-webjs' });
62+
const firstSentence = description.slice(0, description.indexOf('. ') + 1);
63+
assert.ok(firstSentence.length <= 160, `first sentence is ${firstSentence.length} chars, over the 160-char snippet window`);
64+
assert.ok(firstSentence.includes('plain language'), 'the plain-language claim is inside the snippet window, not past it');
65+
});
66+
3867
test('why metadata is self-consistent and points at the dedicated /why-webjs social card', () => {
3968
const m = generateMetadata({ url: 'https://webjs.dev/why-webjs' });
4069
assert.equal(m.openGraph.title, m.title, 'og:title matches the <title>');

0 commit comments

Comments
 (0)