docs: make the component sections executable in one pass
Expands 3.1 and 3.2 so the work can be done without re-deriving anything. 3.1 now records what 0.8.6 already fixed, separated into the ten components that were miswired and the five that gained outputs they had been firing undeclared, with the caveat that Map's three were converted but never confirmed in a browser. For the 22 that remain it adds the finding that changes the decision: all nine are pure scaffolds with no state, functions or handlers, and five of them duplicate a component that already works -- FileUpload against FileInput and FileUploadProgress, Toast and ToastNotifications against Toaster, AdvancedDatePicker against DatePicker, AdvancedRangeSlider against RangeSlider. Superseding those is a migration entry rather than new code, and leaves Chart, TreeView, Confetti and CopyMarkup as the only ones needing to be built. 3.2 corrects the scaffold count from 23 to 28; the earlier figure used a looser rule. Nine of the 28 are the 3.1 components, so the two items must be planned together, and several of the rest are primitives that need only their styles moved out of ui.css rather than any behaviour. Also corrects the dead-output component count from 11 to 9 in both documents. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
// POST /api/ai { "prompt": "..." } → Claude's reply.
|
||||
// Set ANTHROPIC_API_KEY in your environment (e.g. a .env file) to enable this.
|
||||
import { createAI } from "@wrnexus/ai";
|
||||
import type { Context } from "@wrnexus/core";
|
||||
|
||||
const ai = createAI(); // reads ANTHROPIC_API_KEY; defaults to claude-opus-4-8
|
||||
|
||||
export const POST = async (ctx: Context) => {
|
||||
if (!process.env.ANTHROPIC_API_KEY) {
|
||||
return Response.json({ error: "Set ANTHROPIC_API_KEY to use AI." }, { status: 501 });
|
||||
}
|
||||
const { prompt } = await ctx.req.json().catch(() => ({}));
|
||||
if (!prompt) return Response.json({ error: "Provide a 'prompt'." }, { status: 400 });
|
||||
|
||||
// Stream the reply back as plain text. Use `ai.generate(prompt)` for a one-shot string.
|
||||
return ai.streamResponse(prompt);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export const GET = async () => {
|
||||
return Response.json({ message: "Hello API" });
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
// A reusable component. Route: none — mounted inside a page with
|
||||
// <div data-component="counter" ...props></div>.
|
||||
//
|
||||
// Components render on the SERVER (with their props applied) and are hydrated in
|
||||
// the browser by the generic reactive runtime — they ship no JS of their own.
|
||||
component Counter {
|
||||
// Props arrive as mount attributes, each coerced to the type of its default
|
||||
// (so start="5" arrives as the number 5).
|
||||
props {
|
||||
start = 0
|
||||
label = "Count"
|
||||
}
|
||||
|
||||
// State can reference props. `count` seeds the reactive scope.
|
||||
state count = start
|
||||
|
||||
view {
|
||||
<button @click="count++" class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm transition hover:bg-indigo-500 active:scale-[0.98]">{label}: {count}</button>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Create application tables here.
|
||||
-- Run with: bunx wrnexus db migrate
|
||||
@@ -0,0 +1,2 @@
|
||||
// Add deterministic development seed data here.
|
||||
export async function seed(): Promise<void> {}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Global document layout. The framework renders this once around the selected
|
||||
// page layout and merges SEO metadata, styles, and scripts into <head>/<body>.
|
||||
// Request cookies, resolved theme, language, URL, and pathname are available
|
||||
// as SSR props, so document attributes do not need a client-side correction.
|
||||
layout Document {
|
||||
props {
|
||||
cookies = {}
|
||||
theme = "light"
|
||||
language = "en"
|
||||
url = ""
|
||||
pathname = "/"
|
||||
}
|
||||
|
||||
view {
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<div id="app"><slot /></div>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"common": {
|
||||
"appName": "admin",
|
||||
"welcome": "Welcome to admin"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { Middleware } from "@wrnexus/core";
|
||||
|
||||
const logger: Middleware = async (ctx, next) => {
|
||||
console.log(ctx.req.method, ctx.url.pathname);
|
||||
return next();
|
||||
};
|
||||
|
||||
export default logger;
|
||||
@@ -0,0 +1,20 @@
|
||||
page About {
|
||||
seo {
|
||||
title = "About"
|
||||
description = "Learn how admin is built with WrNexus."
|
||||
}
|
||||
|
||||
view {
|
||||
<main class="min-h-screen bg-white px-6 py-20 text-slate-900 dark:bg-[#0b0f1e] dark:text-slate-100">
|
||||
<article class="mx-auto max-w-2xl">
|
||||
<a href="/" class="text-sm text-indigo-600 hover:underline dark:text-indigo-400">← Home</a>
|
||||
<p class="mt-12 font-mono text-xs uppercase tracking-[0.2em] text-indigo-500">WrNexus application</p>
|
||||
<h1 class="mt-4 text-4xl font-bold tracking-tight">About admin</h1>
|
||||
<p class="mt-6 text-lg leading-8 text-slate-600 dark:text-slate-400">
|
||||
This page is server-rendered from <code>app/pages/about.wrn</code>. Add state,
|
||||
events, components, APIs, and data without switching to another UI framework.
|
||||
</p>
|
||||
</article>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Home page (route: /). SSR-first: the view is server-rendered, then components
|
||||
// (.wrn files under app/components) hydrate in the browser. Styled with Tailwind.
|
||||
page Home {
|
||||
seo {
|
||||
title = "Home"
|
||||
description = "admin — built with WrNexus, an SSR-first Bun framework."
|
||||
}
|
||||
|
||||
view {
|
||||
<main class="relative min-h-screen overflow-hidden bg-white text-slate-900 dark:bg-[#0b0f1e] dark:text-slate-100">
|
||||
<div aria-hidden="true" class="pointer-events-none absolute inset-x-0 -top-40 mx-auto h-96 max-w-2xl rounded-full bg-indigo-500/20 blur-3xl"></div>
|
||||
|
||||
<div class="relative mx-auto flex min-h-screen max-w-3xl flex-col px-6">
|
||||
<header class="flex items-center justify-between py-6">
|
||||
<span class="flex items-center gap-2.5 font-semibold tracking-tight">
|
||||
<span class="grid h-7 w-7 place-items-center rounded-md bg-gradient-to-br from-indigo-500 to-violet-600 text-sm font-bold text-white">W</span>
|
||||
admin
|
||||
</span>
|
||||
<button data-wire-theme-toggle class="rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-600 transition hover:border-slate-300 hover:text-slate-900 dark:border-white/10 dark:text-slate-400 dark:hover:border-white/20 dark:hover:text-white">
|
||||
Toggle theme
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section class="flex flex-1 flex-col items-center justify-center py-16 text-center">
|
||||
<p class="font-mono text-xs uppercase tracking-[0.2em] text-indigo-500 dark:text-indigo-400">SSR-first · Bun-native</p>
|
||||
|
||||
<h1 class="mt-5 text-4xl font-bold leading-[1.1] tracking-tight sm:text-6xl">
|
||||
Server-rendered.<br />
|
||||
Instantly <span class="bg-gradient-to-r from-indigo-500 to-violet-500 bg-clip-text text-transparent">interactive</span>.
|
||||
</h1>
|
||||
|
||||
<p class="mt-5 max-w-md text-base leading-relaxed text-slate-600 dark:text-slate-400">
|
||||
admin runs on WrNexus — write <code class="rounded bg-slate-100 px-1.5 py-0.5 font-mono text-[0.85em] text-slate-800 dark:bg-white/10 dark:text-slate-200">.wrn</code> components, ship no client boilerplate, and let the server do the work.
|
||||
</p>
|
||||
|
||||
<div class="mt-8 flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="/about" class="rounded-lg bg-slate-900 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition hover:bg-slate-700 dark:bg-white dark:text-slate-900 dark:hover:bg-slate-200">Get started</a>
|
||||
<a href="/api/hello" class="rounded-lg border border-slate-200 px-5 py-2.5 text-sm font-medium text-slate-700 transition hover:border-slate-300 dark:border-white/10 dark:text-slate-300 dark:hover:border-white/20">View API</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-14 w-full max-w-md rounded-2xl border border-slate-200 bg-white p-6 text-left shadow-sm dark:border-white/10 dark:bg-white/5">
|
||||
<div class="flex items-center gap-2 font-mono text-xs text-slate-400">
|
||||
<span class="h-2 w-2 rounded-full bg-emerald-400"></span>
|
||||
live · hydrated on the server
|
||||
</div>
|
||||
<div class="mt-4 flex items-center justify-between gap-4">
|
||||
<div data-component="counter" start="0" label="Clicks"></div>
|
||||
<span class="max-w-[10rem] text-right text-xs leading-snug text-slate-500">This button works. You wrote zero client JavaScript.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="mt-10 font-mono text-xs text-slate-400 dark:text-slate-600">
|
||||
edit <span class="text-slate-600 dark:text-slate-400">app/pages/index.wrn</span> to make it yours
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<footer class="border-t border-slate-100 py-6 text-center text-xs text-slate-400 dark:border-white/5 dark:text-slate-600">
|
||||
Built with <a href="https://www.npmjs.com/package/@wrnexus/cli" class="text-slate-600 underline-offset-2 hover:underline dark:text-slate-400">WrNexus</a>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// ws://<host>/realtime/chat — a simple broadcast room.
|
||||
//
|
||||
// The client side is the framework's realtime runtime; a page opts in with
|
||||
// `data-room="chat"`. Here we only handle room events.
|
||||
//
|
||||
// client.send(msg) → just this connection
|
||||
// client.broadcast(msg) → everyone else in the room
|
||||
// client.room.broadcast(msg) → everyone, including the sender
|
||||
import { defineRoom } from "@wrnexus/core";
|
||||
|
||||
export default defineRoom({
|
||||
onConnect(client) {
|
||||
client.send({ type: "system", text: "connected" });
|
||||
},
|
||||
|
||||
onMessage(client, msg) {
|
||||
// Echo each message to the whole room so every tab stays in sync.
|
||||
client.room.broadcast({ type: "message", data: msg });
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
import { v } from "@wrnexus/validation";
|
||||
|
||||
export const contactSchema = v.object({
|
||||
email: v.string().email(),
|
||||
message: v.string().min(10).max(2_000),
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
import { implement } from "../../../../../../packages/rpc/src/index.ts";
|
||||
import { catalogService } from "../../../../packages/shared/src/index.ts";
|
||||
|
||||
/** Private service consumed by the workspace's web app. */
|
||||
export default implement(
|
||||
catalogService,
|
||||
{
|
||||
getProduct: ({ sku }) => ({ sku, name: "WRNexus Sta
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Global stylesheet. Tailwind v4 is compiled by the styles.process hook in
|
||||
* wrnexus.config.ts and served at /__wrnexus/styles.css on every page.
|
||||
*
|
||||
* @source tells Tailwind which files to scan for class names.
|
||||
*/
|
||||
@import "tailwindcss";
|
||||
@plugin "@iconify/tailwind4";
|
||||
@source "../**/*.wrn";
|
||||
@source "../**/*.tsx";
|
||||
|
||||
/* Make Tailwind's `dark:` variant follow the framework's data-theme attribute
|
||||
* (set on <html> by the theme system), not the OS setting. Any element with
|
||||
* data-wire-theme-toggle flips it. */
|
||||
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
||||
|
||||
body {
|
||||
font-family: var(--wire-font-sans, "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif);
|
||||
}
|
||||
Reference in New Issue
Block a user