Skip to content

Commit 3416d59

Browse files
committed
Dynamic Apps docs polish, GitHub source links, generateObject
- Mirror the dynamic-apps docs overhaul into vendor (Generate/Deploy/Route, Connect to Rivet, Workloads two-section pattern, Advanced group) - New ExampleLinkBar docs component; code snippets now show a View source on GitHub button next to copy (rehype sourceUrl + repoForContentPath) - Landing/blog/example snippets use generateObject; landing card and copy cleanups; worker production checklist gains a Node.js LTS item
1 parent 1db1663 commit 3416d59

31 files changed

Lines changed: 728 additions & 386 deletions

File tree

.claude/skills/launch-graphics/graphics/snippets-dynamic-apps.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
"title": "1. Generate",
44
"language": "ts",
5-
"code": "import { anthropic } from \"@ai-sdk/anthropic\";\nimport { generateText, Output } from \"ai\";\nimport { z } from \"zod\";\n\nconst { output } = await generateText({\n model: anthropic(\"claude-sonnet-4-5\"),\n output: Output.object({\n schema: z.object({ files: z.record(z.string(), z.string()) }),\n }),\n prompt: \"Build a team board as a complete web app.\",\n});"
5+
"code": "import { anthropic } from \"@ai-sdk/anthropic\";\nimport { generateObject } from \"ai\";\nimport { z } from \"zod\";\n\nconst { object } = await generateObject({\n model: anthropic(\"claude-sonnet-5\"),\n schema: z.object({ files: z.record(z.string(), z.string()) }),\n prompt: \"Build a team board as a complete web app.\",\n});"
66
},
77
{
88
"title": "2. Deploy",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
// "Prefer to read code?" bar with a View-on-GitHub button, extracted from the
3+
// inline HTML in the agentOS quickstart so every product's quickstart can use it.
4+
interface Props {
5+
href: string;
6+
label?: string;
7+
}
8+
const { href, label = "Prefer to read code? Clone the example repository." } = Astro.props;
9+
---
10+
11+
<div
12+
style="border-radius:0.75rem;border:1px solid rgba(27,25,22,0.12);background:rgba(27,25,22,0.035);padding:0.875rem 1.125rem;margin:1.5rem 0;color:#56524a;display:flex;align-items:center;justify-content:space-between;gap:1.25rem;"
13+
>
14+
<span>{label}</span>
15+
<a
16+
href={href}
17+
style="appearance:none;border:1px solid rgba(27,25,22,0.18);background:transparent;color:#1b1916;font-family:var(--sl-font);font-size:0.8rem;font-weight:600;display:inline-flex;align-items:center;justify-content:center;height:2rem;padding:0 0.85rem;border-radius:6px;cursor:pointer;white-space:nowrap;text-decoration:none;flex:none;gap:0.45rem;box-sizing:border-box;"
18+
>
19+
<svg viewBox="0 0 496 512" width="14" height="14" fill="currentColor" aria-hidden="true"
20+
><path
21+
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
22+
></path></svg
23+
>View on GitHub
24+
</a>
25+
</div>

src/components/marketing/diagrams/dynamicAppsDiagrams.tsx

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ const useDiagram = () => {
4242
: {
4343
initial: { opacity: 0 },
4444
animate: inView ? { opacity: 1 } : { opacity: 0 },
45-
transition: { duration: 0.3, delay: 0.1 + order * 0.16, ease: 'easeOut' as const },
45+
transition: { duration: 0.45, delay: 0.1 + order * 0.08, ease: 'easeOut' as const },
4646
};
4747
return { ref, inView, reduced, show };
4848
};
@@ -163,7 +163,7 @@ export const AppsRoutingDiagram = () => {
163163
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
164164
(async () => {
165165
// Let the staged fade-in settle before the first request.
166-
await sleep(1400);
166+
await sleep(1900);
167167
let i = 0;
168168
const step = async (s: RoutingStage, ms: number) => {
169169
if (!alive) return false;
@@ -544,37 +544,47 @@ export const AppsIsolationDiagram = () => {
544544
const publish = () => setSlots([...running]);
545545
let requests = 0;
546546

547+
// Each isolate runs its own lifecycle so several can serve concurrently.
548+
const run = async (isolate: Isolate, slot: number) => {
549+
setLog((lines) => [isolate, ...lines].slice(0, LOG_LINES));
550+
running[slot] = isolate;
551+
publish();
552+
await wait(250);
553+
if (cancelled) return;
554+
// Isolate answers: the log line completes.
555+
const done = { ...isolate, done: true };
556+
setLog((lines) => lines.map((line) => (line.id === isolate.id ? done : line)));
557+
running[slot] = done;
558+
publish();
559+
// The isolate lingers briefly, then the slot empties.
560+
await wait(430);
561+
if (cancelled) return;
562+
if (running[slot]?.id === isolate.id) running[slot] = null;
563+
publish();
564+
};
565+
566+
// Apps still visible in the log or a slot are excluded, so the same app
567+
// never shows up twice at once.
568+
const recentLog: string[] = [];
547569
(async () => {
548570
await wait(500);
549571
while (!cancelled) {
550572
const free = running.map((r, index) => (r ? -1 : index)).filter((index) => index >= 0);
551-
const app = ISOLATION_APPS[Math.floor(rand() * ISOLATION_APPS.length)];
552-
if (free.length === 0 || running.some((r) => r?.app === app)) {
553-
await wait(200);
573+
const candidates = ISOLATION_APPS.filter(
574+
(name) => !running.some((r) => r?.app === name) && !recentLog.includes(name),
575+
);
576+
if (free.length === 0 || candidates.length === 0) {
577+
await wait(70);
554578
continue;
555579
}
580+
const app = candidates[Math.floor(rand() * candidates.length)];
556581
requests += 1;
557582
const isolate: Isolate = { id: requests, app, ok: requests % FAIL_EVERY !== 0, ms: 6 + Math.floor(rand() * 28), done: false };
558583
const slot = free[Math.floor(rand() * free.length)];
559-
560-
// Request lands: log line appears, isolate appears.
561-
setLog((lines) => [isolate, ...lines].slice(0, LOG_LINES));
562-
running[slot] = isolate;
563-
publish();
564-
await wait(750);
565-
if (cancelled) break;
566-
// Isolate answers: the log line completes.
567-
const done = { ...isolate, done: true };
568-
setLog((lines) => lines.map((line) => (line.id === isolate.id ? done : line)));
569-
running[slot] = done;
570-
publish();
571-
// The isolate lingers briefly, then the slot empties.
572-
setTimeout(() => {
573-
if (cancelled) return;
574-
if (running[slot]?.id === isolate.id) running[slot] = null;
575-
publish();
576-
}, 1300);
577-
await wait(700);
584+
recentLog.unshift(app);
585+
recentLog.length = Math.min(recentLog.length, LOG_LINES);
586+
void run(isolate, slot);
587+
await wait(160 + Math.floor(rand() * 240));
578588
}
579589
})();
580590

src/components/marketing/dynamic-apps/AppsAnnotatedCode.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export interface AnnotatedCodeRegion {
1414
}
1515

1616
export interface AppsAnnotatedCodeProps {
17-
fileName: string;
1817
regions: AnnotatedCodeRegion[];
1918
documentationUrl: string;
2019
sourceUrl: string;
@@ -31,12 +30,8 @@ const FooterArrow = () => (
3130
</svg>
3231
);
3332

34-
export const AppsAnnotatedCode = ({ fileName, regions, documentationUrl, sourceUrl }: AppsAnnotatedCodeProps) => (
33+
export const AppsAnnotatedCode = ({ regions, documentationUrl, sourceUrl }: AppsAnnotatedCodeProps) => (
3534
<figure className='overflow-hidden rounded-xl border border-ink/10 bg-white'>
36-
<div className='flex items-center border-b border-ink/10 px-5 py-3 sm:px-6'>
37-
<span className='font-mono text-xs text-ink'>{fileName}</span>
38-
</div>
39-
4035
<div className='divide-y divide-ink/10'>
4136
{regions.map((region, index) => (
4237
<div key={region.label ?? index} className='grid gap-3 px-5 py-5 sm:px-6 md:grid-cols-[minmax(10rem,1fr)_minmax(0,2fr)] md:gap-10 lg:gap-14'>

src/components/v2/Code.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
faTerminal,
2222
faTypescript,
2323
Icon,
24+
faGithub,
2425
} from "@rivet-gg/icons";
2526
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
2627
import escapeHTML from "escape-html";
@@ -187,6 +188,7 @@ export const pre = ({
187188
file,
188189
language,
189190
title,
191+
sourceUrl,
190192
isInGroup,
191193
code,
192194
highlightedCode,
@@ -245,8 +247,20 @@ export const pre = ({
245247
<span data-code-icon className="hidden">
246248
<Icon icon={langIcon} className="size-3" />
247249
</span>
248-
<div className="absolute right-2 top-2 z-10 opacity-0 transition-opacity group-hover/code:opacity-100">
250+
<div className="absolute right-2 top-2 z-10 flex items-center gap-1 opacity-0 transition-opacity group-hover/code:opacity-100">
249251
<TooltipProvider>
252+
{sourceUrl ? (
253+
<WithTooltip
254+
trigger={
255+
<Button asChild size="icon-sm" variant="ghost" className="text-ink-soft hover:bg-ink/10 hover:text-ink">
256+
<a href={sourceUrl} target="_blank" rel="noreferrer">
257+
<Icon icon={faGithub} />
258+
</a>
259+
</Button>
260+
}
261+
content="View source on GitHub"
262+
/>
263+
) : null}
250264
<WithTooltip
251265
trigger={
252266
<CopyCodeTrigger>

src/content/posts/2026-08-31-introducing-dynamic-apps/page.mdx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,12 @@ We've made it incredibly simple to generate code with AI and deploy it for your
2727

2828
```ts @nocheck
2929
import { anthropic } from "@ai-sdk/anthropic";
30-
import { generateText, Output } from "ai";
30+
import { generateObject } from "ai";
3131
import { z } from "zod";
3232

33-
const { output } = await generateText({
34-
model: anthropic("claude-sonnet-4-5"),
35-
output: Output.object({
36-
schema: z.object({ files: z.record(z.string(), z.string()) }),
37-
}),
33+
const { object } = await generateObject({
34+
model: anthropic("claude-sonnet-5"),
35+
schema: z.object({ files: z.record(z.string(), z.string()) }),
3836
prompt: "Build a team board as a complete web app.",
3937
});
4038
```
@@ -46,7 +44,7 @@ import { deployApp } from "@rivet-dev/dynamic-apps";
4644

4745
await deployApp({
4846
appId: "pied-piper",
49-
files: output.files,
47+
files: object.files,
5048
});
5149
```
5250

src/content/self-host/workers/production-checklist.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Split by runtime mode, because the two halves barely overlap. A reader on a serv
1111

1212
## Environment
1313

14+
- **Run the latest Node.js LTS.** Workers should run on the current LTS release; older majors miss performance and security fixes and are not tested against.
1415
- **Set `NODE_ENV=production`.** Ensures optimized performance and disables development-only behavior.
1516
- **Ensure log level is not set to debug.** Leave `RIVET_LOG_LEVEL` at its default or explicitly set it to `warn` to avoid excessive logging.
1617
- **Do not set `RIVET_EXPOSE_ERRORS=1` in production.** This exposes internal error details to clients. It is automatically enabled when `NODE_ENV=development`.

src/mdx/rehype.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import rehypeMermaid from "rehype-mermaid";
77
import * as shiki from "shiki";
88
import { visit } from "unist-util-visit";
99
import theme from "../lib/textmate-code-theme";
10+
import { repoForContentPath } from "../sitemap/docs-sources.node";
1011

1112
function rehypeParseCodeBlocks() {
12-
return (tree) => {
13+
return (tree, vfile) => {
14+
const contentPath = vfile?.path ?? vfile?.history?.[0];
1315
visit(tree, "element", (node, _nodeIndex, parentNode) => {
1416
if (node.tagName === "code") {
1517
// Parse language from className
@@ -118,6 +120,10 @@ function rehypeParseCodeBlocks() {
118120
}
119121
if (sourceFile) {
120122
parentNode.properties.sourceFile = sourceFile;
123+
const repo = repoForContentPath(contentPath);
124+
if (repo) {
125+
parentNode.properties.sourceUrl = `https://github.com/rivet-dev/${repo}/blob/main/${sourceFile}`;
126+
}
121127
}
122128
}
123129
}

0 commit comments

Comments
 (0)