Code
    Chronicle VisualizerUpdated Jun 4, 2026JSX

    Chronicle Visualizer app page

    Imported React file from app/page.jsx.

    Source file

    app/page.jsx

    React176 lines
    1. "use client";
    2. import { useMemo, useState } from "react";
    3. const projects = ["all", "hyphenomenon", "rps-etsy", "family-shapes", "tmora", "shopify", "codex-skills"];
    4. const frames = Array.from({ length: 72 }).map((_, index) => {
    5. const hour = 12 - Math.floor(index / 12);
    6. const minute = 55 - ((index * 5) % 60);
    7. const project = projects[(index % (projects.length - 1)) + 1];
    8. const status = index % 9 === 0 ? "quarantine" : index % 4 === 0 ? "candidate" : "private";
    9. return {
    10. id: `frame-${index + 1}`,
    11. time: `${Math.max(7, hour)}:${String(Math.max(0, minute)).padStart(2, "0")} ${hour >= 12 ? "PM" : "AM"}`,
    12. group: hour >= 11 ? "June 4, 2026 - Midday Capture" : "June 4, 2026 - Morning Capture",
    13. project,
    14. status,
    15. summary: project === "tmora"
    16. ? "Inferred TMORA print-production context. Raw screen content stays private until reviewed."
    17. : `Inferred ${project} work context. Publication requires redaction review and source-safe summary text.`
    18. };
    19. });
    20. const statusLabels = {
    21. private: "raw private",
    22. candidate: "redaction candidate",
    23. quarantine: "quarantined"
    24. };
    25. export default function Home() {
    26. const [project, setProject] = useState("all");
    27. const [query, setQuery] = useState("");
    28. const [selected, setSelected] = useState(frames[7]);
    29. const visibleFrames = useMemo(() => {
    30. const normalized = query.trim().toLowerCase();
    31. return frames.filter((frame) => {
    32. const projectMatch = project === "all" || frame.project === project;
    33. const text = `${frame.project} ${frame.status} ${frame.summary}`.toLowerCase();
    34. return projectMatch && (!normalized || text.includes(normalized));
    35. });
    36. }, [project, query]);
    37. const groups = useMemo(() => {
    38. return visibleFrames.reduce((acc, frame) => {
    39. acc[frame.group] ||= [];
    40. acc[frame.group].push(frame);
    41. return acc;
    42. }, {});
    43. }, [visibleFrames]);
    44. return (
    45. <main className="viewer-shell">
    46. <aside className="side-nav" aria-label="Chronicle viewer sections">
    47. <a className="brand" href="/">
    48. <span className="brand-mark" aria-hidden="true" />
    49. Chronicle
    50. </a>
    51. <nav>
    52. <a className="active" href="/">Timeline</a>
    53. <a href="/docs">Docs</a>
    54. <a href="/projects">Projects</a>
    55. </nav>
    56. <div className="storage-note">
    57. <strong>Raw archive</strong>
    58. <span>Private object storage planned. Git is for source and reviewed public derivatives.</span>
    59. </div>
    60. </aside>
    61. <section className="timeline-app">
    62. <header className="app-toolbar">
    63. <div>
    64. <h1>Visual Timeline</h1>
    65. <p>Public app scaffold using synthetic/redacted placeholders. Raw captures are not loaded here.</p>
    66. </div>
    67. <div className="toolbar-controls">
    68. <label className="app-search">
    69. <span>Search</span>
    70. <input
    71. value={query}
    72. onChange={(event) => setQuery(event.target.value)}
    73. placeholder="project, redaction status, summary"
    74. />
    75. </label>
    76. <button type="button">Archive setup</button>
    77. <button type="button">Redaction queue</button>
    78. </div>
    79. </header>
    80. <section className="filter-row" aria-label="Project filters">
    81. {projects.map((item) => (
    82. <button
    83. className={project === item ? "selected" : ""}
    84. key={item}
    85. onClick={() => setProject(item)}
    86. type="button"
    87. >
    88. {item === "all" ? "All projects" : item}
    89. </button>
    90. ))}
    91. </section>
    92. <div className="viewer-content">
    93. <section className="timeline-canvas" aria-label="Chronicle visual timeline">
    94. {Object.entries(groups).map(([group, items]) => (
    95. <section className="frame-group" key={group}>
    96. <header>
    97. <h2>{group}</h2>
    98. <span>{items.length} frames</span>
    99. </header>
    100. <div className="frame-grid">
    101. {items.map((frame, index) => (
    102. <button
    103. className={`frame-card ${frame.status} ${selected?.id === frame.id ? "is-selected" : ""}`}
    104. key={frame.id}
    105. onClick={() => setSelected(frame)}
    106. type="button"
    107. >
    108. <span className="synthetic-screen">
    109. <span className="screen-bar" />
    110. <span className="screen-line short" />
    111. <span className="screen-line" />
    112. <span className={index % 5 === 0 ? "redaction-block wide" : "redaction-block"} />
    113. </span>
    114. <span className="frame-time">{frame.time}</span>
    115. <span className="frame-project">{frame.project}</span>
    116. </button>
    117. ))}
    118. </div>
    119. </section>
    120. ))}
    121. </section>
    122. <aside className="detail-panel" aria-label="Selected frame details">
    123. <div className="date-rail" aria-label="Date scrubber">
    124. <a href="#june-4" className="active">Jun 4</a>
    125. <a href="#summaries">Summaries</a>
    126. <a href="#archive">Archive</a>
    127. </div>
    128. {selected ? (
    129. <article className="inspector">
    130. <div className={`status-pill ${selected.status}`}>{statusLabels[selected.status]}</div>
    131. <h2>{selected.time}</h2>
    132. <p>{selected.summary}</p>
    133. <dl>
    134. <div>
    135. <dt>Project label</dt>
    136. <dd>{selected.project} <span>inferred</span></dd>
    137. </div>
    138. <div>
    139. <dt>Source visibility</dt>
    140. <dd>private raw frame</dd>
    141. </div>
    142. <div>
    143. <dt>Publication state</dt>
    144. <dd>{selected.status === "candidate" ? "needs review" : "not public"}</dd>
    145. </div>
    146. </dl>
    147. <section className="policy-box">
    148. <h3>Publication boundary</h3>
    149. <p>
    150. This app must publish only reviewed redacted derivatives. Raw screenshots, OCR,
    151. local paths, and private summaries stay outside git and public deploys.
    152. </p>
    153. </section>
    154. </article>
    155. ) : null}
    156. </aside>
    157. </div>
    158. </section>
    159. </main>
    160. );
    161. }