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
18 changes: 15 additions & 3 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Single source of truth for "what's next." One milestone per PR/run. Autopilot:
pick the one task under **NEXT**, ship it, stop. Do **not** start anything under
**BLOCKED**.

_Last updated: 2026-07-04 — Milestone 6 (Web Worker WASM inference) done; three
_Last updated: 2026-07-04 — Milestone 6 (Web Worker WASM inference) done; four
user-directed polish items (boot loading bar, a black+green "coding" aesthetic,
then a persistent title + author credit) landed on top; queue empty pending an
M7 proposal._
a persistent title + author credit, then a bottom "Built with Claude Fable 5"
build credit) landed on top; queue empty pending an M7 proposal._

---

Expand Down Expand Up @@ -216,6 +216,18 @@ M7 proposal._
a real build (centered green title + credit above the orbiting cloud, ~21fps).
Pre-change HEAD (rollback) `d5546e8`.

- **Polish — bottom "Built with Claude Fable 5" build credit (user-directed, not
a milestone).** A bottom-middle build-attribution line, the counterpart to the
top `#app-title`. Same treatment: an inline `#build-credit` div in `index.html`
styled in `src/style.css` (`position: fixed; bottom: 12px`, centered, dim
`--accent-dim` monospace), `pointer-events: none` + `z-index: 5` so it never
intercepts orbit drags and the boot loader still covers it during boot. **No
JS.** Smoke test asserts the string ships in the raw HTML AND the live credit
is visible, horizontally centered near the bottom, and doesn't capture pointer
events over the canvas. Proven non-tautological (RED on the prior HTML: no
`#build-credit`). Verified visually on a real build. Pre-change HEAD (rollback)
`94b4084`.

**Carry-over facts from M3/M4 (do not re-derive):**

- Depth output contract: `depth` RawImage, Uint8, 1 channel, input W×H, 0–255
Expand Down
6 changes: 6 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@
<div class="app-title-name">Live Depth Field</div>
<div class="app-title-credit">by Noah Federovitch</div>
</div>
<!--
Build attribution, bottom-middle of the screen — counterpart to #app-title.
Static chrome (inlined, not built by JS), pointer-events:none so it never
blocks orbit drags over the canvas.
-->
<div id="build-credit">Built with Claude Fable 5</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
17 changes: 17 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,23 @@ canvas {
color: var(--accent-dim);
}

/* Build attribution, docked bottom-middle over the canvas — counterpart to
#app-title. Same pointer-events:none / below-boot-loader rules so it never
steals orbit drags and the loader still covers it during boot. */
#build-credit {
position: fixed;
bottom: 12px;
left: 50%;
transform: translateX(-50%);
z-index: 5;
text-align: center;
pointer-events: none;
user-select: none;
font: 11px/1 var(--mono);
letter-spacing: 0.08em;
color: var(--accent-dim);
}

/* Milestone 2: live splat-uniform sliders, docked top-left over the canvas. */
#controls {
position: fixed;
Expand Down
61 changes: 61 additions & 0 deletions tests/smoke.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,67 @@ test('title: Live Depth Field name + Noah Federovitch credit shown at top center
expect(errors, `unexpected page errors:\n${errors.join('\n')}`).toEqual([]);
});

// Build credit (user-directed, not a milestone). A "Built with Claude Fable 5"
// attribution must sit at the bottom-middle of the screen — the counterpart to
// the top #app-title. Static presentational chrome inlined in the initial HTML,
// so this asserts (a) it ships in the raw server bytes, (b) it's visible,
// horizontally centered near the BOTTOM of the live page, and (c) the overlay
// never intercepts pointer input (so it can't block orbiting the cloud beneath
// it). Non-tautological: no #build-credit exists before this change.
test('build credit: "Built with Claude Fable 5" shown at bottom center', async ({
page,
}) => {
const errors = [];
page.on('pageerror', (e) => errors.push(`pageerror: ${e}`));
page.on('console', (m) => {
if (m.type() === 'error') errors.push(`console.error: ${m.text()}`);
});

// (a) The credit ships in the exact bytes the server sends — static chrome,
// not JS-injected. Fetched raw so a scripted node can't satisfy it.
const html = await (await page.request.get('/')).text();
expect(html, 'initial HTML must contain a #build-credit overlay').toMatch(
/id=["']build-credit["']/,
);
expect(html, 'the credit must name the build tool').toContain(
'Built with Claude Fable 5',
);

// (b) Live: the credit is visible and its text is present once the page loads.
await page.goto('/');
const credit = page.locator('#build-credit');
await expect(credit).toBeVisible();
await expect(credit).toContainText(/Built with Claude Fable 5/i);

// Bottom-middle placement: horizontally centered, near the bottom edge.
const box = await credit.boundingBox();
const viewport = page.viewportSize();
const creditCenter = box.x + box.width / 2;
const viewportCenter = viewport.width / 2;
expect(
Math.abs(creditCenter - viewportCenter),
`credit must be horizontally centered (center ${creditCenter} vs ${viewportCenter})`,
).toBeLessThan(40);
expect(
box.y + box.height,
'credit must sit near the bottom of the screen',
).toBeGreaterThan(viewport.height - 80);

// (c) The overlay must not steal orbit input — the element under its center
// point must be the canvas, not the credit.
const underCenter = await page.evaluate(() => {
const el = document.getElementById('build-credit');
const r = el.getBoundingClientRect();
const hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
return hit ? hit.tagName.toLowerCase() : null;
});
expect(underCenter, 'credit must not capture pointer events over the canvas').toBe(
'canvas',
);

expect(errors, `unexpected page errors:\n${errors.join('\n')}`).toEqual([]);
});

// Milestone 6: WASM inference moves into a Web Worker so the main thread stays
// responsive during a pass. The worker itself can't run in CI (it loads the
// real ~100MB model — the worker-path proof is a local real-model probe, like
Expand Down
Loading