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
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The site is its own pnpm workspace (`docs/pnpm-workspace.yaml`), so its dependen
```
src/styles/tokens.css theme colors for light and dark; every color on the site comes from here
src/styles/global.css base type, layout wrapper, pills, cards, buttons
src/data/ links, supported agents, and the feature cards
src/data/ links and supported agents
src/components/ one component per section; Hero and DemoCard hold the interactive parts
src/scripts/ circuit.ts (hero background) and demo.ts (the demo card loop)
src/layouts/Base.astro page shell, fonts, meta tags
Expand All @@ -39,7 +39,7 @@ Each page under `src/content/docs/docs/` is a Markdown file served at `/docs/<pa

The CLI reference is generated. `pnpm dev` and `pnpm build` regenerate it from `createProgram` in `src/cli` first, so it lists exactly what the CLI parses. That step needs the osq package's dependencies installed at the repository root. Without them it keeps the committed page and prints a warning, so commit the regenerated page whenever a command changes.

Feature cards live in `src/data/features.ts`. `available` lists only what ships in the current npm release and `roadmap` what the Rust rebuild adds. Move a card when it ships, never before.
The feature list lives in the docs, in `src/content/docs/docs/index.md`. "What you get" lists only what ships in the current npm release and "Coming in the rebuild" what the Rust rebuild adds. Move an item when it ships, never before.

## Later

Expand Down
35 changes: 0 additions & 35 deletions docs/src/components/FeatureGrid.astro

This file was deleted.

46 changes: 0 additions & 46 deletions docs/src/components/Features.astro

This file was deleted.

8 changes: 7 additions & 1 deletion docs/src/components/GetStarted.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import { links } from '../data/site';
import { harnesses, links } from '../data/site';
---

<section id="start">
Expand All @@ -8,6 +8,10 @@ import { links } from '../data/site';
<span class="eyebrow">Get started</span>
<h2>Running in a few minutes</h2>
<p class="lede">osq needs Node.js 24 or newer and one supported coding agent. It adds a few folders to your repo and nothing else.</p>
<div class="agents">
<span>Works with</span>
{harnesses.map((name) => <span class="pill">{name}</span>)}
</div>
<div class="oss">
<h3>Open source</h3>
<p>osq is MIT licensed and developed in the open. Everything it knows about your project lives in plain files in your own repository, so you can read it, diff it, or walk away with it.</p>
Expand Down Expand Up @@ -37,6 +41,8 @@ pnpm osq land 042</pre>
.start { display: grid; grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 48px; align-items: start; }
.intro { display: grid; gap: 16px; }
.lede { font-size: 16px; }
.agents { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 14px; color: var(--muted); }
.agents .pill { font-size: 13px; padding: 6px 12px; font-weight: 500; }
.oss { display: grid; gap: 12px; margin-top: 8px; }
.oss p { color: var(--muted); font-size: 15px; }
pre { margin: 0; background: var(--code-bg); color: var(--code-fg); font-family: var(--mono); font-size: 13.5px; line-height: 1.7; padding: 20px; border-radius: 8px; overflow-x: auto; }
Expand Down
92 changes: 60 additions & 32 deletions docs/src/components/HowItWorks.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import Ledger from './Ledger.astro';

const steps = [
{ who: 'Anyone', you: true, title: 'Write a brief', body: 'A business goal in plain words, or a ticket copied from your project management backlog.' },
{ who: 'An engineer and a strong model', you: true, title: 'Spec it', body: 'Turns the brief into a spec AI can build: small tasks, each with the files it may touch and the test that proves it.' },
{ who: 'An engineer', you: true, title: 'Approve', body: 'A one-screen summary flags anything risky. Nothing runs before an engineer says yes.' },
{ who: 'Anyone', you: true, title: 'Write a brief', body: 'What you want and why, in plain words. No technical detail: a business goal or a backlog ticket is enough.' },
{ who: 'An engineer and a strong model', you: true, title: 'Spec it', body: 'Turns the brief into a detailed technical spec: small tasks, each with the files it may touch and the test that proves it.' },
{ who: 'An engineer', you: true, title: 'Approve', body: 'Every change in the spec is color-coded by risk, so the risky ones stand out. Nothing runs before an engineer says yes.' },
{ who: 'Coding agents', title: 'Build', body: 'One fresh agent per task. A failed task retries with the reason, or comes back to you.' },
{ who: 'osq', title: 'Verify and land', body: 'Re-tests the whole change, updates the specs, and merges it into your main branch.' },
];
Expand Down Expand Up @@ -69,9 +69,25 @@ const leaves = [38.8, 41, 43.2].map((x) => box(x - 0.6, x + 0.6, -5, -5, 8.4, 9.
const gate = { back: path([58, -5, 0], [58, -5, 15]), front: path([58, 5, 0], [58, 5, 15]), beam: path([58, -5.5, 15], [58, 5.5, 15]) };
// the bar sits low across the belt and lifts straight up to the top beam to let the box through
const barrier = path([58, -5, 6.6], [58, 5, 6.6]);
// 4. two robot arms, one from behind the belt and one from the front
const armBack = { base: box(75, 79, -11, -7, 0, 3), d: path([77, -9, 3], [77, -9, 13], [80, -4, 17], [82, -0.5, 12]), joints: [[77, -9, 13], [80, -4, 17]] as V[], hand: [82, -0.5, 12] as V };
const armFront = { base: box(86, 90, 7, 11, 0, 3), d: path([88, 9, 3], [88, 9, 12], [86, 4, 16], [83, 0.5, 11.5]), joints: [[88, 9, 12], [86, 4, 16]] as V[], hand: [83, 0.5, 11.5] as V };
// 4. a lamp behind the belt whose head reaches over the box and rains code down into it
const LIT = 18.3;
const lamp = {
base: box(80.5, 85.5, -12.5, -6, 0, 1.4),
post: box(82.3, 83.7, -10.2, -8.8, 1.4, 19),
head: box(81.2, 84.8, -10.8, 1.6, 19, 20.8),
shade: box(81.6, 84.4, -3.4, 1.2, LIT, 19),
};
// a sheet of light from the shade to the box's top, as wide on screen as the box
const beam = pts([81.7, 1.3, LIT], [84.3, -1.3, LIT], [84.3, -1.3, BELT + 2.6], [81.7, 1.3, BELT + 2.6]);
const fall = (LIT - BELT - 2.6) * S;
// columns of falling bits, in px from the shade's centre; each column loops on its own clock
const rain = [
{ x: -13, d: 1.1, delay: -0.2, bits: '10110' },
{ x: -6.5, d: 0.8, delay: -0.6, bits: '01101' },
{ x: 0, d: 1.0, delay: -0.1, bits: '11010' },
{ x: 6.5, d: 0.9, delay: -0.5, bits: '00111' },
{ x: 13, d: 1.2, delay: -0.8, bits: '10011' },
];
// 5. the scanner arch and the crate that is main
const scanner = { back: box(105, 107, -6, -4, 0, 13), front: box(105, 107, 4, 6, 0, 13), top: box(105, 107, -6, 6, 13, 15) };
const scanPlane = pts([106, -3.6, 12.6], [106, 3.6, 12.6], [106, 3.6, BELT + 0.2], [106, -3.6, BELT + 0.2]);
Expand All @@ -80,7 +96,7 @@ const landed = [137, 141.5].map((x) => box(x - 1.3, x + 1.3, -4.7, -2.1, 8, 10.6
const crateLabel = p([140, 6, 3.2]);
const retry = (() => {
const [ax, ay] = p([106, 0, 17]);
const [bx, by] = p([84, 0, 21]);
const [bx, by] = p([84, 0, 23.5]);
return { d: `M${ax.toFixed(1)} ${ay.toFixed(1)} C${(ax - 10).toFixed(1)} ${(ay - 70).toFixed(1)} ${(bx + 40).toFixed(1)} ${(by - 60).toFixed(1)} ${bx.toFixed(1)} ${by.toFixed(1)}`, label: [(ax + bx) / 2 + 10, Math.min(ay, by) - 52] };
})();
const humans = [
Expand All @@ -90,7 +106,7 @@ const badges = [
{ v: [3.7, 5, 17] as V, you: true },
{ v: [31, -8.5, 22] as V, you: true },
{ v: [58, 0, 21] as V, you: true },
{ v: [83, 0, 24] as V, you: false },
{ v: [83, 0, 28] as V, you: false },
{ v: [106, 0, 20] as V, you: false },
];
---
Expand All @@ -103,7 +119,7 @@ const badges = [
<figure class="factory">
<div class="scroller">
<svg viewBox="0 0 1150 500" role="img" aria-labelledby="factory-title">
<title id="factory-title">An isometric factory line. People plan a brief around a table and send it down a pipe, where it lands on the belt as a task box, an engineer and a model turn it into a spec of small tasks, an engineer opens the gate, two robot arms build each task, and a scanner checks every one before it lands in main. A failed task loops back to the arms.</title>
<title id="factory-title">An isometric factory line. People plan a brief around a table and send it down a pipe, where it lands on the belt as a task box, an engineer and a model turn it into a spec of small tasks, an engineer opens the gate, a lamp rains code down into each task to build it, and a scanner checks every one before it lands in main. A failed task loops back to the lamp.</title>

<!-- floor -->
<polygon class="slab-top" points={box(-4, 146, -12, 12, -1.5, 0).top} />
Expand All @@ -128,14 +144,13 @@ const badges = [
{leaves.map((pt) => <polygon class="leaf" points={pt} />)}
<circle class="blink" r="3.5" transform={at([39, -5, 3])} />

<polygon class="m-top" points={armBack.base.top} />
<polygon class="m-front" points={armBack.base.front} />
<polygon class="m-side" points={armBack.base.side} />
<g class="arm a1">
<path class="limb" d={armBack.d} />
{armBack.joints.map((v) => <circle class="joint" r="4" transform={at(v)} />)}
<path class="grip" transform={at(armBack.hand)} d="M-6 0 H6 M-6 0 V7 M6 0 V7" />
</g>
{[lamp.base, lamp.post].map((b) => (
<g>
<polygon class="m-top" points={b.top} />
<polygon class="m-front" points={b.front} />
<polygon class="m-side" points={b.side} />
</g>
))}

<path class="post" d={gate.back} />
<polygon class="m-top" points={scanner.back.top} />
Expand Down Expand Up @@ -220,15 +235,23 @@ const badges = [
<path class="post" d={gate.beam} />
<circle class="lamp" r="5" transform={at([58, 0, 17.5])} />

<!-- the front arm -->
<polygon class="m-top" points={armFront.base.top} />
<polygon class="m-front" points={armFront.base.front} />
<polygon class="m-side" points={armFront.base.side} />
<g class="arm a2">
<path class="limb" d={armFront.d} />
{armFront.joints.map((v) => <circle class="joint" r="4" transform={at(v)} />)}
<path class="grip" transform={at(armFront.hand)} d="M-6 0 H6 M-6 0 V7 M6 0 V7" />
<!-- the lamp's head over the belt, and the code it rains into the box -->
<polygon class="beam" points={beam} />
<g transform={at([83, 0, LIT])}>
<clipPath id="rain-clip"><rect x="-22" y="1" width="44" height={(fall - 5).toFixed(1)} /></clipPath>
<g class="rain" clip-path="url(#rain-clip)">
{rain.map((c) => (
<g class="col" style={`animation-duration: ${c.d}s; animation-delay: ${c.delay}s`}>
{[...c.bits].map((b, i) => <text x={c.x} y={i * 10} style={`opacity: ${(i + 1) / c.bits.length}`} class:list={{ lead: i === c.bits.length - 1 }}>{b}</text>)}
</g>
))}
</g>
</g>
<polygon class="m-top" points={lamp.head.top} />
<polygon class="m-front" points={lamp.head.front} />
<polygon class="m-side" points={lamp.head.side} />
<polygon class="shade" points={lamp.shade.front} />
<polygon class="shade" points={lamp.shade.side} />

<!-- the scanner: front post, top, and the plane that checks each task -->
<polygon class="scan" points={scanPlane} />
Expand Down Expand Up @@ -327,11 +350,12 @@ const badges = [
.b3 { animation-name: talk3; }
.barrier { fill: none; stroke: var(--accent); stroke-width: 5; stroke-linecap: round; animation: gate 16s ease-in-out infinite; }
.lamp { animation: lamp 16s linear infinite; }
.limb { fill: none; stroke: var(--muted); stroke-width: 5; stroke-linecap: round; stroke-linejoin: round; }
.joint { fill: var(--surface); stroke: var(--accent); stroke-width: 2; }
.grip { fill: none; stroke: var(--fg); stroke-width: 2.5; stroke-linecap: round; }
.arm { animation: work 16s ease-in-out infinite; }
.a2 { animation-name: work2; }
.shade { fill: var(--accent-soft); stroke: var(--accent); stroke-width: 1; animation: shade 16s linear infinite; }
.beam { fill: var(--accent); opacity: 0; animation: beam 16s linear infinite; }
.rain { opacity: 0; animation: rain 16s linear infinite; }
.col { animation: fall 1s linear infinite; }
.col text { fill: var(--accent); font: 500 10px var(--mono); text-anchor: middle; }
.col text.lead { fill: var(--fg); }
.scan { fill: var(--green-fg); opacity: 0.12; stroke: var(--green-fg); stroke-width: 1.5; animation: scan 16s linear infinite; }
.retry { fill: none; stroke: var(--red-fg); stroke-width: 1.5; stroke-dasharray: 4 5; opacity: 0.8; animation: flow 1.2s linear infinite; }
.note { fill: var(--muted); font: 13px var(--sans); }
Expand Down Expand Up @@ -379,8 +403,12 @@ const badges = [
@keyframes leaf { 0%, 19% { opacity: 0; } 22%, 96% { opacity: 1; } 100% { opacity: 0; } }
@keyframes gate { 0%, 41% { transform: translateY(0); } 44%, 51% { transform: translateY(-52px); } 55%, 100% { transform: translateY(0); } }
@keyframes lamp { 0%, 41.9% { fill: var(--red-fg); } 42%, 52.9% { fill: var(--green-fg); } 53%, 100% { fill: var(--red-fg); } }
@keyframes work { 0%, 51% { transform: translateY(0); } 54% { transform: translateY(8px); } 57% { transform: translateY(0); } 60% { transform: translateY(8px); } 63% { transform: translateY(0); } 100% { transform: translateY(0); } }
@keyframes work2 { 0%, 52.5% { transform: translateY(0); } 55.5% { transform: translateY(8px); } 58.5% { transform: translateY(0); } 61.5% { transform: translateY(8px); } 64.5%, 100% { transform: translateY(0); } }
/* the lamp lights while the box sits under it, 51-66% */
@keyframes shade { 0%, 50% { fill: var(--accent-soft); } 52%, 64% { fill: var(--accent); } 66%, 100% { fill: var(--accent-soft); } }
@keyframes beam { 0%, 50% { opacity: 0; } 52%, 64% { opacity: 0.1; } 66%, 100% { opacity: 0; } }
@keyframes rain { 0%, 51% { opacity: 0; } 52.5%, 64% { opacity: 1; } 65.5%, 100% { opacity: 0; } }
/* each column starts just above the shade and falls through the box's lid */
@keyframes fall { from { translate: 0 -46px; } to { translate: 0 80px; } }
@keyframes scan { 0%, 71% { opacity: 0.08; } 73%, 78% { opacity: 0.35; } 80%, 100% { opacity: 0.08; } }

ol { list-style: none; margin: 24px 0 0; padding: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 24px; }
Expand Down
9 changes: 6 additions & 3 deletions docs/src/components/Ledger.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ const feeds = columns.map((x) => {
const mid = 500 + (x - 500) * 0.06;
return x === 500 ? 'M500 0 V120' : `M${x} 0 V36 L${mid} 96 V120`;
});
// when each step happens in the factory's 16s loop (see HowItWorks), so a step's record drops as it acts
const fires = [0.02, 0.2, 0.44, 0.58, 0.88].map((t) => `${(t * 16).toFixed(2)}s`);
---

<div class="ledger">
<svg class="feeds" viewBox="0 0 1000 120" preserveAspectRatio="none" aria-hidden="true">
{feeds.map((d, i) => (
<g>
<path class="feed" d={d} vector-effect="non-scaling-stroke" />
<path class="pulse" d={d} pathLength="100" vector-effect="non-scaling-stroke" style={`animation-delay: ${(-i * 0.6).toFixed(1)}s`} />
<path class="pulse" d={d} pathLength="100" vector-effect="non-scaling-stroke" style={`animation-delay: ${fires[i]}`} />
</g>
))}
</svg>
Expand Down Expand Up @@ -53,7 +55,7 @@ const feeds = columns.map((x) => {
.ledger { margin-top: 8px; display: flex; flex-direction: column; align-items: center; }
.feeds { display: block; width: 100%; height: 96px; }
.feed { fill: none; stroke: color-mix(in srgb, var(--accent) 35%, transparent); stroke-width: 1.5; }
.pulse { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 8 92; animation: down 3s linear infinite; }
.pulse { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-dasharray: 8 92; opacity: 0; animation: down 16s linear infinite; }
.db { width: 64px; margin-top: -2px; }
.db svg { display: block; width: 100%; height: auto; }
.side, .cap { fill: var(--surface); stroke: var(--accent); stroke-width: 2; }
Expand All @@ -66,7 +68,8 @@ const feeds = columns.map((x) => {
h4 { margin: 0; font-family: var(--display); font-size: 16px; font-weight: 600; }
.item p { font-size: 14px; color: var(--muted); }
code { font-family: var(--mono); font-size: 12px; color: var(--muted); }
@keyframes down { from { stroke-dashoffset: 8; } to { stroke-dashoffset: -92; } }
/* one trip down in the first 10% of the loop, then the line rests until its step comes round again */
@keyframes down { 0% { stroke-dashoffset: 8; opacity: 1; } 10% { stroke-dashoffset: -92; opacity: 1; } 10.1%, 100% { stroke-dashoffset: -92; opacity: 0; } }
/* the feeds line up with five columns; once the steps wrap there is nothing to line up with */
@media (max-width: 900px) { .feeds { display: none; } .ledger { margin-top: 40px; } }
@media (max-width: 560px) { .cards { grid-template-columns: minmax(0, 1fr); } }
Expand Down
Loading
Loading