Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion website/app/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const TITLE = 'WebJs: Full-stack web components framework, no build step';
// The true version of that idea is about LOCATION, not volume: the source sits
// in the app's own node_modules at the installed version, so an agent opens
// the file it needs instead of recalling an API from training data.
const DESCRIPTION = 'An AI-first full-stack JavaScript web components framework with no build step. Production-ready architecture from your very first prompt. Node 24+ or Bun.';
const DESCRIPTION = 'A full-stack JavaScript web components framework with no build step. Production-ready architecture from your very first prompt. Node 24+ or Bun.';

const NAV = [
{ label: 'Docs', href: DOCS_START_PATH, ext: false },
Expand Down
325 changes: 152 additions & 173 deletions website/app/page.ts

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions website/app/what-is-webjs/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ const TITLE = 'What is WebJs?';
// Front-loaded so the first 160 characters answer the query on their own, which
// is what a SERP snippet and an AI answer engine both quote.
const DESCRIPTION =
'WebJs is an AI-first full-stack JavaScript web framework built on web components. It renders on the server, ships no build step, and runs on Node 24+ or Bun. Pages are real HTML that work before any script loads.';
'WebJs is a full-stack JavaScript web framework built on web components. It renders on the server, ships no build step, and runs on Node 24+ or Bun. Pages are real HTML that work before any script loads.';

/**
* The visible FAQ. Rendered into the page AND parsed into FAQPage JSON-LD from
Expand All @@ -53,7 +53,7 @@ const FAQ = [
{
question: 'What is WebJs?',
answer:
'WebJs is an AI-first full-stack JavaScript web framework built on web components. It server-renders every page and component to real HTML, needs no build step or bundler, and runs on Node 24+ or Bun. The framework serves your source to the browser exactly as you wrote it, so the code you read is the code that runs.',
'WebJs is a full-stack JavaScript web framework built on web components. It server-renders every page and component to real HTML, needs no build step or bundler, and runs on Node 24+ or Bun. The framework serves your source to the browser exactly as you wrote it, so the code you read is the code that runs.',
},
{
question: 'What makes WebJs AI-first?',
Expand Down Expand Up @@ -120,7 +120,7 @@ export function generateMetadata() {
image: `${SITE_URL}/public/og-what.png`,
'image:width': '1200',
'image:height': '630',
'image:alt': 'What is WebJs? An AI-first full-stack JavaScript web framework built on web components',
'image:alt': 'What is WebJs? A full-stack JavaScript web framework built on web components',
'site_name': 'WebJs',
},
twitter: {
Expand Down Expand Up @@ -239,7 +239,7 @@ export default function WhatIsWebJs() {
What is WebJs?
</h1>
<p class="text-hero-lede leading-[1.3] text-fg-muted max-w-[64rem] mx-auto mb-6 text-balance">
<strong class="text-fg font-semibold">WebJs is an AI-first full-stack JavaScript web
<strong class="text-fg font-semibold">WebJs is a full-stack JavaScript web
framework built on web components.</strong> It server-renders every page and component to
real HTML, needs no build step or bundler, and runs on Node 24+ or Bun.
</p>
Expand Down
10 changes: 5 additions & 5 deletions website/app/why-webjs/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const ARRIVES = [
},
{
title: 'Type safety, end to end',
body: 'Types run the whole way through. A component importing a server function keeps that function\'s argument and return types at the call site, and a database row carries its schema type into the markup that renders it, with no code generation anywhere in between. An agent has no reason to reach for any.',
body: 'Types run the whole way through. A component importing a server function keeps that function\'s argument and return types at the call site, and a database row carries its schema type into the markup that renders it, with no code generation anywhere in between.',
},
{
title: 'Database',
Expand All @@ -118,8 +118,8 @@ export default function Why() {
The framework your AI agent already understands
</h1>
<p class="text-hero-lede leading-[1.3] text-fg-muted max-w-[64rem] mx-auto mb-6 text-balance">
<strong class="text-fg font-semibold">WebJs is a full-stack JavaScript framework with no
build step, so nothing is hidden from your agent.</strong> The framework ships in
<strong class="text-fg font-semibold">WebJs is a full-stack JavaScript web components
framework with no build step, so nothing is hidden from your agent.</strong> The framework ships in
node_modules as plain JavaScript, at the version your app installed.
</p>
<p class="text-base leading-[1.7] text-fg-muted max-w-[56ch] mx-auto mb-8 text-pretty">
Expand Down Expand Up @@ -223,7 +223,7 @@ Counter.register('counter');
</div>
<div class="grid grid-cols-1 wide:grid-cols-2 gap-4 items-stretch max-w-3xl mx-auto mb-12">
<div class="flex flex-col min-w-0">
<p class="font-mono font-semibold text-xs leading-[1.4] tracking-widest uppercase text-fg-subtle mb-2.5 ml-1">Where the decisions are still open</p>
<p class="font-mono font-semibold text-xs leading-[1.4] tracking-widest uppercase text-fg-subtle mb-2.5 ml-1">Other frameworks</p>
<figure class=${WIN}>
<figcaption class=${WINBAR}>${DOTS}<span class=${WINNAME}>prompt</span></figcaption>
<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="A prompt that has to specify the architecture as well as the app"><code><span class="text-accent">&gt;</span> Build me a table booking app
Expand All @@ -234,7 +234,7 @@ Counter.register('counter');
</figure>
</div>
<div class="flex flex-col min-w-0">
<p class="font-mono font-semibold text-xs leading-[1.4] tracking-widest uppercase text-fg-subtle mb-2.5 ml-1">Where they are already made</p>
<p class="font-mono font-semibold text-xs leading-[1.4] tracking-widest uppercase text-fg-subtle mb-2.5 ml-1">WebJs</p>
<figure class=${WIN}>
<figcaption class=${WINBAR}>${DOTS}<span class=${WINNAME}>prompt</span></figcaption>
<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 same request on WebJs, with no architecture appended"><code><span class="text-accent">&gt;</span> Build me a table booking app
Expand Down
Binary file modified website/public/og-what.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/public/og-why.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/public/og.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion website/scripts/generate-og-what.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const TITLE = 'What is <span class="accent">WebJs</span>?';

// The definition sentence from the page, which is also the first 160 characters
// of its meta description, so the card and the search result agree.
const SUB = '<b>An AI-first full-stack JavaScript web framework built on web components,</b> server-rendered with no build step.';
const SUB = '<b>A full-stack JavaScript web framework built on web components,</b> server-rendered with no build step.';

// The definition above says what it IS. These say what that MEANS, and they
// carry the feature terms the site-wide meta description gave up to fit.
Expand Down
2 changes: 1 addition & 1 deletion website/scripts/generate-og-why.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const TITLE = 'The framework your <span class="accent">AI agent</span> already u
// node_modules at the installed version, so the agent opens the file it is
// calling instead of recalling an API from training data. That is also the
// stronger claim, because it holds no matter how large the framework grows.
const SUB = '<b>A full-stack JavaScript framework</b> with no build step, so nothing is hidden from your agent.';
const SUB = '<b>A full-stack JavaScript web components framework</b> with no build step, so nothing is hidden from your agent.';

const FACTS = [
{
Expand Down
2 changes: 1 addition & 1 deletion website/scripts/generate-og.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const TITLE_HTML = TITLE.replace('Production-ready', '<span class="accent">Produ

// What the thing IS, under what it PROMISES. The lockup already says the name,
// so the sentence opens on the category instead of repeating it.
const SUB = '<b>An AI-first full-stack JavaScript web components framework</b> with no build step.';
const SUB = '<b>A full-stack JavaScript web components framework</b> with no build step.';

// The two halves of the promise above, each stated as something checkable. The
// first is the site's own account of what a scaffold decides for you; the
Expand Down
66 changes: 0 additions & 66 deletions website/test/ssr/intro-video-ssr.test.ts

This file was deleted.

63 changes: 63 additions & 0 deletions website/test/ssr/landing-copy-ssr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* The landing page used to carry an intro video, first as a YouTube iframe and
* then (#1440) as a native player pointed at videos.webjs.dev. It is gone.
*
* The bytes are still in the R2 bucket `webjs-videos`, so restoring the section
* is an editorial call rather than a re-upload. What these assertions pin is
* that it does not come back by accident: an embed added to any of the shared
* section helpers would land on this page without anyone editing it here.
*
* The copy assertions cover the one-line definition of WebJs, which is
* duplicated across the visible lede, the meta description, and the OG-image
* generator. Pinning the rendered wording is what catches a partial edit that
* leaves the search snippet contradicting the page.
*/
import test from 'node:test';
import assert from 'node:assert/strict';
import { renderToString } from '@webjsdev/core/server';
import LandingPage from '#app/page.ts';

const render = () => renderToString(LandingPage());

test('the landing page embeds no video of any kind', async () => {
const out = await render();
assert.doesNotMatch(out, /<video/, 'no native player');
assert.doesNotMatch(out, /<iframe/, 'no embed of any kind');
assert.doesNotMatch(out, /videos\.webjs\.dev/, 'no reference to the video host');
assert.doesNotMatch(out, /youtube-nocookie|youtube\.com\/embed/i, 'no embed url');
assert.doesNotMatch(out, /intro-video/, 'no leftover section or styling hook');
});

test('the hero lede leads with what WebJs is, without the AI-first label', async () => {
const out = await render();
assert.match(
out,
/WebJs is a full-stack JavaScript web components framework with no build step\./,
);
assert.doesNotMatch(out, /AI-first full-stack/, 'the adjective is out of the definition');
});

test('the web-components section pays its DX claim in checkable nouns', async () => {
const out = await render();
// The heading promises "better DX", which is the one kind of claim this
// section's own rules forbid leaving unpaid: it needs a baseline the page
// never states. The lede has to cash it in things the stage below visibly
// does, so pin those rather than the adjective.
assert.match(out, /Native web components the browser already understands/);
for (const paid of ['custom element', 'signals', 'decorator', 'build step']) {
assert.ok(out.includes(paid), `the lede pays the DX claim with ${paid}`);
}
});

test('the progressive-enhancement and elision claims survive the heading change', async () => {
const out = await render();
// The section stopped LEADING with these when it was renamed. They are still
// the only place the page states either, and the P.S. dare below invites the
// reader to check the first one, so a chip going missing is a real loss and
// not a copy tidy.
assert.ok(out.includes('Reads and submits before JS'), 'the no-JS claim is still on the page');
assert.ok(out.includes('Display components ship 0 KB'), 'the elision claim is still on the page');
assert.ok(out.includes('No whole-page hydration'), 'the precise hydration wording, not a shorter one');
assert.doesNotMatch(out, /No hydration (runtime|overhead)/, 'those read as zero cost and are false');
assert.ok(out.includes('P.S. Turn JavaScript off'), 'the dare that lets a reader verify it');
});
12 changes: 10 additions & 2 deletions website/test/ssr/why-webjs-ssr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('the pitch page SSRs with its headline, terminals, reason cards, and a main
const out = await renderToString(Why());
assert.ok(out.length > 1000, 'renders substantial HTML');
assert.ok(out.includes('already understands'), 'includes the hero headline');
assert.ok(out.includes('full-stack JavaScript framework'), 'states the full-stack framework category');
assert.match(out, /full-stack JavaScript web\s+components\s+framework/, 'states the web-components framework category');
assert.ok(out.includes('npm create webjs@latest my-app'), 'includes the install command');
assert.ok(out.includes('No training data required'), 'includes the core pitch reason');
assert.ok(out.includes('node_modules/@webjsdev/core/src'), 'includes the read-the-source terminal proof');
Expand All @@ -43,7 +43,15 @@ test('the two prompts differ only by the architecture one of them has to spell o
// property that makes the contrast real: the appended instructions appear in
// one prompt and in neither the other prompt nor as something WebJs asks for.
const out = await renderToString(Why());
const open = out.slice(out.indexOf('aria-label="A prompt that has to specify'), out.indexOf('Where they are already made'));
// Both slices are bounded by aria-labels rather than by the visible column
// headings, which are editorial and have been renamed once. A missing
// boundary returns -1, which silently widens the slice to the rest of the
// document and makes every includes() below pass vacuously, so assert the
// boundaries exist before slicing on them.
for (const boundary of ['aria-label="A prompt that has to specify', 'aria-label="The same request on WebJs', 'Both prompts should produce']) {
assert.ok(out.includes(boundary), `the slice boundary ${boundary} still exists`);
}
const open = out.slice(out.indexOf('aria-label="A prompt that has to specify'), out.indexOf('aria-label="The same request on WebJs'));
const made = out.slice(out.indexOf('aria-label="The same request on WebJs'), out.indexOf('Both prompts should produce'));

const ask = 'Build me a table booking app';
Expand Down
Loading