page
"use client"; import { useMemo, useState } from "react"; const projects = ["all", "hyphenomenon", "rps-etsy", "family-shapes", "tmora", "shopi...
"use client"; import { useMemo, useState } from "react"; const projects = ["all", "hyphenomenon", "rps-etsy", "family-shapes", "tmora", "shopify", "codex-skills"]; const frames = Array. from({ length: 72 }).
"use client";
import { useMemo, useState } from "react";
const projects = ["all", "hyphenomenon", "rps-etsy", "family-shapes", "tmora", "shopify", "codex-skills"];
const frames = Array.from({ length: 72 }).map((_, index) => {
const hour = 12 - Math.floor(index / 12);
const minute = 55 - ((index * 5) % 60);
const project = projects[(index % (projects.length - 1)) + 1];
const status = index % 9 === 0 ? "quarantine" : index % 4 === 0 ? "candidate" : "private";
return {
id: frame-${index + 1},
time: ${Math.max(7, hour)}:${String(Math.max(0, minute)).padStart(2, "0")} ${hour >= 12 ? "PM" : "AM"},
group: hour >= 11 ? "June 4, 2026 - Midday Capture" : "June 4, 2026 - Morning Capture",
project,
status,
summary: project === "tmora"
? "Inferred TMORA print-production context. Raw screen content stays private until reviewed."
: Inferred ${project} work context. Publication requires redaction review and source-safe summary text.
};
});
const statusLabels = { private: "raw private", candidate: "redaction candidate", quarantine: "quarantined" };
export default function Home() { const [project, setProject] = useState("all"); const [query, setQuery] = useState(""); const [selected, setSelected] = useState(frames[7]);
const visibleFrames = useMemo(() => {
const normalized = query.trim().toLowerCase();
return frames.filter((frame) => {
const projectMatch = project === "all" || frame.project === project;
const text = ${frame.project} ${frame.status} ${frame.summary}.toLowerCase();
return projectMatch && (!normalized || text.includes(normalized));
});
}, [project, query]);
const groups = useMemo(() => { return visibleFrames.reduce((acc, frame) => { acc[frame.group] ||= []; acc[frame.group].push(frame); return acc; }, {}); }, [visibleFrames]);
return ( <main className="viewer-shell"> <aside className="side-nav" aria-label="Chronicle viewer sections"> <a className="brand" href="/"> <span className="brand-mark" aria-hidden="true" /> Chronicle </a> <nav> <a className="active" href="/">Timeline</a> <a href="/docs">Docs</a> <a href="/projects">Projects</a> </nav> <div className="storage-note"> <strong>Raw archive</strong> <span>Private object storage planned. Git is for source and reviewed public derivatives.</span> </div> </aside>
<section className="timeline-app">
<header className="app-toolbar">
<div>
<h1>Visual Timeline</h1>
<p>Public app scaffold using synthetic/redacted placeholders. Raw captures are not loaded here.</p>
</div>
<div className="toolbar-controls">
<label className="app-search">
<span>Search</span>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="project, redaction status, summary"
/>
</label>
<button type="button">Archive setup</button>
<button type="button">Redaction queue</button>
</div>
</header>
<section className="filter-row" aria-label="Project filters">
{projects.map((item) => (
<button
className={project === item ? "selected" : ""}
key={item}
onClick={() => setProject(item)}
type="button"
>
{item === "all" ? "All projects" : item}
</button>
))}
</section>
<div className="viewer-content">
<section className="timeline-canvas" aria-label="Chronicle visual timeline">
{Object.entries(groups).map(([group, items]) => (
<section className="frame-group" key={group}>
<header>
<h2>{group}</h2>
<span>{items.length} frames</span>
</header>
<div className="frame-grid">
{items.map((frame, index) => (
<button
className={`frame-card ${frame.status} ${selected?.id === frame.id ? "is-selected" : ""}`}
key={frame.id}
onClick={() => setSelected(frame)}
type="button"
>
<span className="synthetic-screen">
<span className="screen-bar" />
<span className="screen-line short" />
<span className="screen-line" />
<span className={index % 5 === 0 ? "redaction-block wide" : "redaction-block"} />
</span>
<span className="frame-time">{frame.time}</span>
<span className="frame-project">{frame.project}</span>
</button>
))}
</div>
</section>
))}
</section>
<aside className="detail-panel" aria-label="Selected frame details">
<div className="date-rail" aria-label="Date scrubber">
<a href="#june-4" className="active">Jun 4</a>
<a href="#summaries">Summaries</a>
<a href="#archive">Archive</a>
</div>
{selected ? (
<article className="inspector">
<div className={`status-pill ${selected.status}`}>{statusLabels[selected.status]}</div>
<h2>{selected.time}</h2>
<p>{selected.summary}</p>
<dl>
<div>
<dt>Project label</dt>
<dd>{selected.project} <span>inferred</span></dd>
</div>
<div>
<dt>Source visibility</dt>
<dd>private raw frame</dd>
</div>
<div>
<dt>Publication state</dt>
<dd>{selected.status === "candidate" ? "needs review" : "not public"}</dd>
</div>
</dl>
<section className="policy-box">
<h3>Publication boundary</h3>
<p>
This app must publish only reviewed redacted derivatives. Raw screenshots, OCR,
local paths, and private summaries stay outside git and public deploys.
</p>
</section>
</article>
) : null}
</aside>
</div>
</section>
</main>
); }
Provenance
- Source file:
app/page.jsx - Source URL: https://github.com/maggielerman/chronicle-visualizer/blob/main/app/page.jsx
Metadata
- Created
- Not recorded
- Last updated
- Not recorded