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
39 changes: 39 additions & 0 deletions apps/presentation/site/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,45 @@ not raw session logs. The full-screen viewer bundles only the two explicit
live LoopX state, local status feeds, private registries, raw logs, or write
APIs.

## Time Machine

`/lab/time-machine/` is a read-only, decision-first public experience. Choose
for an Agent, see the actual recorded outcome, then optionally inspect the
original finding or browse each capture. The existing homepage research
collection discovers it. English is the default; `?lang=zh` selects Chinese,
and `?case=graph` or `?case=ad` opens another episode. Back/forward navigation
restores the episode. Choices live only in page memory; reloading resets them.
Both choices reveal the same historical outcome. They trigger no model,
installation, restore, live Goal operation or simulated counterfactual.

The three scenes distinguish a Portfolio local/native reversal, a scoped Graph
finding with unproven native benefit, and an Ad screening/fresh-sample reversal.
These are solver-authored diagnostic observations, not independent replications,
countable benchmark results or evidence of a LoopX performance gain. The original
status and text remain visible, including uncertainty. The record slider reads
one original event in append order; it is not a historical-state projection.
There are no inferred dependencies or causal links.

The 76/134/105 events are byte-identical public fixtures from
[the fixed research-history source](https://github.com/loopx-project/loopx/tree/44c7af3db271ab1991428d3ff1991ca922434eff/apps/presentation/dashboard/src/data/fixtures),
selected against the
[immutable public archive](https://huggingface.co/datasets/huangrt01/loopx-edgebench-research-checkpoints-20261015/tree/c858f51021dc1c1cf08263f9c04b692a5e29b40b).
The browser verifies the capture SHA-256 before enabling a choice. A missing,
changed or malformed capture clears the displayed records and offers retry;
old content is never relabeled as another case. No raw native session is bundled.
The existing Explore v0 owner defines record semantics; the local TS reader
only checks the pinned artifact shape and renders its text. No shared vocabulary,
capability or provider is introduced. The route loads separately from the home
page and the static build includes the English question and archive link.

Run `node --test scripts/time-machine.test.mjs` for capture integrity, negative
cases and verbatim order checks, then `npm run build` and the ordinary SEO smoke.
The packaged browser journey must also cover choices/results/original text,
locale/case/history/reload, keyboard and narrow screens, plus a missing or
changed capture and successful retry. Build at both `/` and `/loopx/` to qualify
asset and navigation bases. Removing the route and its research entry reverts
the experience without touching Explore state or live Agents.

## Search publication

`npm run build` compiles the browser app and prerenders the homepage and the
Expand Down
2,717 changes: 2,717 additions & 0 deletions apps/presentation/site/public/lab/time-machine/data/ad.json

Large diffs are not rendered by default.

3,486 changes: 3,486 additions & 0 deletions apps/presentation/site/public/lab/time-machine/data/graph.json

Large diffs are not rendered by default.

1,952 changes: 1,952 additions & 0 deletions apps/presentation/site/public/lab/time-machine/data/portfolio.json

Large diffs are not rendered by default.

18 changes: 15 additions & 3 deletions apps/presentation/site/scripts/seo-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ for (const [, href] of skill.matchAll(/\]\((https:\/\/[^)]+)\)/g)) {
}
}
const titles = new Set();
for (const [path, subject] of [["", "long-running"], ["benchmarks/swe-marathon/", "SWE-Marathon"], ["benchmarks/lhtb/", "LHTB"]]) {
for (const [path, subject] of [["", "long-running"], ["benchmarks/swe-marathon/", "SWE-Marathon"], ["benchmarks/lhtb/", "LHTB"], ["lab/time-machine/", "Time Machine"]]) {
const html = await readFile(resolve(directory, path, "index.html"), "utf8");
const title = html.match(/<title>(.*?)<\/title>/s)?.[1];
assert(title?.toLowerCase().includes(subject.toLowerCase()), `${path}: descriptive title`);
assert(!titles.has(title), "page titles must be distinct");
titles.add(title);
assert.equal((html.match(/<h1(?:\s|>)/g) ?? []).length, 1, `${path}: one static primary heading`);
assert((html.match(/<h2(?:\s|>)/g) ?? []).length >= 2, `${path}: real sections before JavaScript`);
assert((html.match(/<h2(?:\s|>)/g) ?? []).length >= (path === "lab/time-machine/" ? 1 : 2), `${path}: real sections before JavaScript`);
assert(html.includes('id="root">') && !html.includes('id="root"></div>'), `${path}: populated React root`);
assert.equal((html.match(/rel="canonical"/g) ?? []).length, 1);
assert(html.includes(`rel="canonical" href="${origin}${path}"`), `${path}: canonical matches route`);
Expand All @@ -42,6 +42,18 @@ for (const [path, subject] of [["", "long-running"], ["benchmarks/swe-marathon/"
const structured = JSON.parse(html.match(/<script type="application\/ld\+json">(.*?)<\/script>/s)?.[1] ?? "null");
assert.equal(structured?.["@type"], "SoftwareSourceCode");
assert.equal(structured?.codeRepository, "https://github.com/loopx-project/loopx");
assert(html.includes('lab/time-machine/'), "homepage discovers the interactive research experience");
}
if (path === "lab/time-machine/") {
assert(html.includes("Local results improved."), "question exists before JavaScript");
assert(html.includes("c858f51021dc1c1cf08263f9c04b692a5e29b40b"), "immutable archive remains accessible without JavaScript");
const base = html.match(/<script\b[^>]*\bsrc="([^"]*)site-assets\/[^"]+"/)?.[1];
assert(html.includes(`href="${base}"`), "return link preserves deployment base");
const scenes = JSON.parse(await readFile(new URL("../src/time-machine/scenes.json", import.meta.url)));
for (const scene of scenes) {
assert.equal(await readFile(resolve(directory, path, "data", scene.file), "utf8"),
await readFile(new URL(`../public/lab/time-machine/data/${scene.file}`, import.meta.url), "utf8"), "capture copied unchanged");
}
}
const description = html.match(/<meta name="description" content="([^"]+)"/)?.[1];
assert(description?.length > 50 && description.length < 240, `${path}: useful snippet`);
Expand All @@ -68,4 +80,4 @@ if (process.argv.includes("--with-docs")) {
const index = await readFile(resolve(directory, "sitemap.xml"), "utf8");
for (const [, url] of index.matchAll(/<loc>(.*?)<\/loc>/g)) await access(resolve(directory, url.slice(origin.length)));
}
console.log(`SEO publication: agent skill and owner links, three prerendered routes, ${locations.length} canonical sitemap targets, assets and metadata passed`);
console.log(`SEO publication: agent skill and owner links, four prerendered routes, ${locations.length} canonical sitemap targets, assets and metadata passed`);
64 changes: 64 additions & 0 deletions apps/presentation/site/scripts/time-machine.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from "node:assert/strict";
import {createHash} from "node:crypto";
import {readFile} from "node:fs/promises";
import test from "node:test";
import {readPinnedCapture} from "../src/time-machine/records.ts";

const scenes = JSON.parse(await readFile(new URL("../src/time-machine/scenes.json", import.meta.url)));
const buffer = bytes => bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
const bytesFor = scene => readFile(new URL(`../public/lab/time-machine/data/${scene.file}`, import.meta.url));

test("all three pinned captures preserve original records and explicit evidence anchors", async () => {
assert.deepEqual(scenes.map(scene => scene.count), [76, 134, 105]);
for (const scene of scenes) {
const bytes = await bytesFor(scene);
const source = JSON.parse(bytes);
const rows = await readPinnedCapture(buffer(bytes), scene);
assert.deepEqual(rows, source.events, "the renderer does not rewrite source text, status or time");
assert.equal(source.provenance.trajectory_publication.revision, "c858f51021dc1c1cf08263f9c04b692a5e29b40b");
assert.equal(source.provenance.claim_scope, "diagnostic_only");
assert.equal(source.provenance.score_countable, false);
assert.equal(source.source_counts.edge_count, 0);
assert.equal(source.source_counts.parent_records, 0);
const finding = rows.find(row => row.result_id === scene.evidenceId);
assert.equal(finding.event_kind, "finding");
assert.equal(scene.en.choices.length, 2);
assert.equal(scene.zh.choices.length, 2);
assert.equal(scene.en.metrics.length, scene.zh.metrics.length);
}
});

test("different or edited bytes cannot inherit the curated scene's authority", async () => {
const [portfolio, graph] = await Promise.all(scenes.slice(0, 2).map(bytesFor));
await assert.rejects(readPinnedCapture(buffer(graph), scenes[0]), /digest/);
const edited = Buffer.from(portfolio);
edited[10] ^= 1;
await assert.rejects(readPinnedCapture(buffer(edited), scenes[0]), /digest/);
await assert.rejects(readPinnedCapture(new ArrayBuffer(0), scenes[0]), /digest/);
});

test("artifact-shape failures reject even if an editor updates the digest", async () => {
const original = JSON.parse(await bytesFor(scenes[0]));
for (const mutate of [
data => {data.events[0].schema_version = "invented_schema";},
data => {data.events[0].recorded_at = "not a date";},
data => {data.events[0].goal_id = "another-capture";},
data => {data.events[1].event_id = data.events[0].event_id;},
data => {data.events.splice(data.events.findIndex(row => row.result_id === scenes[0].evidenceId), 1);},
data => {data.source_sha256 = "0".repeat(64);},
]) {
const data = structuredClone(original); mutate(data);
const bytes = Buffer.from(JSON.stringify(data));
await assert.rejects(readPinnedCapture(buffer(bytes), {...scenes[0], sha256: digest(bytes)}));
}
});

test("a record reader preserves append order rather than inventing historical state", async () => {
const data = JSON.parse(await bytesFor(scenes[0]));
data.events[0].recorded_at = "2030-01-01T00:00:00Z";
const bytes = Buffer.from(JSON.stringify(data));
const rows = await readPinnedCapture(buffer(bytes), {...scenes[0], sha256: digest(bytes)});
assert.deepEqual(rows, data.events);
assert.equal(rows[0].recorded_at, "2030-01-01T00:00:00Z");
});
6 changes: 3 additions & 3 deletions apps/presentation/site/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ const content = {
["Research & evaluation", "LHTB × LoopX", "Compare five execution mechanisms across 46 long-horizon terminal tasks and inspect where recoverable state helps.", "Read the study"],
["Research · Chinese", "DeepSWE behavior discoveries", "Explore how domain hints affect implementation and validation in individual cases.", "Read the behavior analysis"],
["Research · Chinese", "DeepSWE × Sol", "Read the historical 113-task results, continuation mechanisms, and patch-delivery checks.", "Read the research brief"],
["Cases", "All LoopX showcases", "Browse public cases, interactive walkthroughs, and their evidence boundaries.", "Browse all cases"],
["Interactive research", "If you were there", "Make a choice for the Agent, then see three real research reversals and inspect their original records.", "Try the Time Machine"],
],
developer: "Projection developer tools",
},
Expand Down Expand Up @@ -266,7 +266,7 @@ const content = {
["研究与评测", "LHTB × LoopX", "对比五种执行机制在 46 个长程终端任务上的结果,观察可恢复状态在什么场景有效。", "阅读研究简报"],
["研究与评测", "DeepSWE 行为分析", "从具体案例观察领域提示如何影响实现选择与验证行为。", "阅读行为分析"],
["研究与评测", "DeepSWE × Sol", "解读 113 任务的历史结果、持续执行机制,以及补丁如何进入最终验收。", "阅读研究简报"],
["案例", "完整案例目录", "浏览公开案例、交互式讲解及其证据边界。", "浏览全部案例"],
["交互体验", "如果你在场", "先替 Agent 做一次选择,再看三个真实研究片段里的反转与原始记录。", "试试 LoopX 时光机"],
],
developer: "开发者投影工具",
},
Expand Down Expand Up @@ -1006,7 +1006,7 @@ export function App() {
`benchmarks/lhtb/${language === "zh" ? "?lang=zh" : ""}`,
"benchmarks/deepswe/behavior-discovery/",
"benchmarks/deepswe-sol/",
`docs/showcases/index${language === "en" ? ".en" : ""}.html`,
`lab/time-machine/${language === "zh" ? "?lang=zh" : ""}`,
];
return (
<a className="learn-card resource-card" key={title} href={`${basePath}${paths[index]}`}>
Expand Down
8 changes: 6 additions & 2 deletions apps/presentation/site/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { StrictMode } from "react";
import { lazy, StrictMode, Suspense } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
import { LhtbBrief } from "./LhtbBrief";
import { SweMarathonBrief } from "./SweMarathonBrief";
import "./styles.css";
import "./swe-marathon-brief.css";
import "./lhtb-brief.css";
import "./time-machine/time-machine.css";

const rootElement = document.getElementById("root");
if (!rootElement) throw new Error("LoopX homepage root is missing");
Expand All @@ -14,9 +15,12 @@ const pathSegments = window.location.pathname.split("/").filter(Boolean);
if (pathSegments.at(-1) === "index.html") pathSegments.pop();
const isSweMarathonBrief = pathSegments.slice(-2).join("/") === "benchmarks/swe-marathon";
const isLhtbBrief = pathSegments.slice(-2).join("/") === "benchmarks/lhtb";
const isTimeMachine = pathSegments.slice(-2).join("/") === "lab/time-machine";
const TimeMachine = lazy(() => import("./time-machine/TimeMachine"));

createRoot(rootElement).render(
<StrictMode>
{isSweMarathonBrief ? <SweMarathonBrief /> : isLhtbBrief ? <LhtbBrief /> : <App />}
{isTimeMachine ? <Suspense fallback={<p role="status">Loading / 正在读取…</p>}><TimeMachine /></Suspense>
: isSweMarathonBrief ? <SweMarathonBrief /> : isLhtbBrief ? <LhtbBrief /> : <App />}
</StrictMode>,
);
11 changes: 11 additions & 0 deletions apps/presentation/site/src/page-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
// Public URLs are independent of the local preview's deployment base.
export const siteUrl = "https://loopx-project.github.io/loopx/";
export const pageMetadata = {
timeMachine: {
path: "lab/time-machine/",
en: {
title: "LoopX Time Machine — If you were there",
description: "Make three choices for a research Agent, then compare them with real recorded outcomes. Inspect the original evidence, failed routes and remaining uncertainty.",
},
zh: {
title: "LoopX 时光机 — 如果你在场",
description: "先替研究 Agent 做选择,再看真实记录里的结果。逐条阅读原始证据、失败路线和仍未回答的问题,体验长程研究如何延续。",
},
},
home: {
path: "",
en: {
Expand Down
3 changes: 2 additions & 1 deletion apps/presentation/site/src/render-static.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { renderToString } from "react-dom/server";
import { App } from "./App";
import { LhtbBrief } from "./LhtbBrief";
import { SweMarathonBrief } from "./SweMarathonBrief";
import TimeMachine from "./time-machine/TimeMachine";
import { pageMetadata, siteUrl, type PublicPage } from "./page-metadata";

export { pageMetadata, siteUrl };

export function render(page: PublicPage) {
return renderToString(page === "home" ? <App /> : page === "lhtb" ? <LhtbBrief /> : <SweMarathonBrief />);
return renderToString(page === "timeMachine" ? <TimeMachine /> : page === "home" ? <App /> : page === "lhtb" ? <LhtbBrief /> : <SweMarathonBrief />);
}
Loading
Loading