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 docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ Pass options after `--`, for example `pnpm test:package-managers -- --only=yarn`
| --- | --- | --- | --- |
| `pnpm examples:react` | `examples/vite-react` (Vite 8, React) | Vite plugin | `http://127.0.0.1:4310` |
| `pnpm examples:vue` | `examples/vite-vue` (Vite 8, Vue) | Vite plugin | `http://127.0.0.1:4320` |
| `pnpm examples:astro` | `examples/astro` (Astro 5) | Vite plugin in `astro.config.mjs` | `http://127.0.0.1:4330` |
| `pnpm examples:astro` | `examples/astro` (Astro 7) | Vite plugin in `astro.config.mjs` | `http://127.0.0.1:4330` |
| `pnpm examples:next` | `examples/next` (Next.js 16 App Router, Turbopack) | `needmark dev -- next dev`, app on a free port | `http://localhost:4340` |
| `pnpm examples:html` | `examples/html` (static files served by `sirv`) | `needmark dev` proxy, app on 4351 | `http://127.0.0.1:4350` |

Expand Down
2 changes: 1 addition & 1 deletion examples/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "astro dev --host 127.0.0.1 --port 4330"
},
"dependencies": {
"astro": "^5.18.2"
"astro": "~7.3.8"
},
"devDependencies": {
"needmark": "workspace:*"
Expand Down
2,649 changes: 603 additions & 2,046 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

145 changes: 96 additions & 49 deletions site/index.html

Large diffs are not rendered by default.

143 changes: 95 additions & 48 deletions site/ja/index.html

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions site/logo-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 9 additions & 9 deletions site/logo-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 85 additions & 1 deletion site/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ for (const button of document.querySelectorAll('[data-copy], .code .copy')) {
try {
await navigator.clipboard.writeText(source.textContent.trim() + '\n');
} catch {
// Clipboard access can be refused; selecting the text lets the reader copy it by hand.
// Clipboard access can be refused; showing and selecting the text lets the reader copy it by hand.
source.closest('[hidden]')?.removeAttribute('hidden');
getSelection().selectAllChildren(source);
return;
}
Expand All @@ -31,3 +32,86 @@ for (const button of tabButtons) {
for (const panel of document.querySelectorAll('[data-panel]')) panel.hidden = panel.dataset.panel !== manager;
});
}

// The hero demo replays the review loop as five scenes. Readers who prefer reduced motion get the finished loop as one still frame instead.
const demo = document.querySelector('[data-demo]');
if (demo) {
const stage = demo.querySelector('.stage');
const find = (selector) => stage.querySelector(selector);
const target = find('[data-target]'), draftText = find('[data-type]'), hint = find('[data-hint]'), badge = find('.badge'), cursor = find('.cursor');
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;
let instant = false, visible = true, wake;

const frame = demo.querySelector('.demo');
new ResizeObserver(() => demo.style.setProperty('--s', String(frame.clientWidth / 640))).observe(frame);
// Scenes only advance while the demo is on screen, so a reader scrolling back finds it where they left it.
new IntersectionObserver(([entry]) => { visible = entry.isIntersecting; if (visible) wake?.(); }).observe(demo);

const wait = (ms) => new Promise((resolve) => (instant ? resolve() : setTimeout(resolve, ms)));
const whenVisible = () => visible || new Promise((resolve) => { wake = resolve; });
const state = (name, on = true) => stage.classList.toggle(name, on);
const count = (value) => { badge.textContent = value; badge.dataset.count = value; };
// Positions are measured from the scaled stage, so the cursor lands on the same spot at any width.
const point = async (element) => {
const box = stage.getBoundingClientRect(), rect = element.getBoundingClientRect(), scale = box.width / 640;
cursor.style.translate = `${(rect.left + rect.width / 2 - box.left) / scale}px ${(rect.top + rect.height / 2 - box.top) / scale}px`;
await wait(650);
};
const press = async () => { state('press'); await wait(140); state('press', false); };
const click = async (element) => { await point(element); await press(); };

const scenes = [
async () => {
await click(find('[data-plus]'));
state('commenting'); hint.textContent = hint.dataset.hint;
await point(target); state('hover'); await wait(300); await press();
state('selected'); state('draft'); await wait(350);
const text = draftText.dataset.type;
for (let i = 1; i <= text.length; i++) { draftText.textContent = text.slice(0, i); await wait(32); }
state('typed'); await click(find('[data-save]'));
state('draft', false); state('hover', false); state('selected', false); state('pinned'); count(1);
hint.textContent = hint.dataset.saved; await wait(1000);
state('commenting', false);
},
async () => {
await click(find('[data-list]')); state('list'); await wait(300);
await click(find('[data-copy]')); state('copied');
},
async () => {
await wait(700); state('list', false); state('copied', false); state('agent-on'); await wait(500);
await click(find('[data-paste]')); state('pasted'); await wait(500);
await click(find('[data-send]')); state('sent');
},
async () => {
state('thinking'); await wait(1400);
state('replied'); state('fixed');
},
async () => {
await wait(1600); state('agent-on', false); await wait(400);
await click(find('[data-list]')); state('list'); await wait(300);
await click(find('[data-resolve]')); state('resolved'); count(0);
},
];

const reset = () => {
stage.className = 'stage instant';
draftText.textContent = ''; hint.textContent = ''; count(0);
cursor.style.translate = '';
};

async function play() {
reset();
instant = true;
if (reduced) { for (const scene of scenes) await scene(); return; }
stage.offsetWidth;
instant = false; stage.classList.remove('instant');
for (;;) {
for (const scene of scenes) { await whenVisible(); await scene(); await wait(600); }
await wait(1800); state('fade'); await wait(450);
reset(); state('fade'); stage.offsetWidth;
stage.classList.remove('instant'); state('fade', false);
}
}

play();
}
Loading
Loading