feat(maestro): wire React app as sole web entry point
Deletes the vanilla shell and mounts App.tsx on #root. main.ts and the 23 views it drove are gone; App.tsx composes the ported components — sidebar, multi-view, command bar, spawn modal, help and restart overlays, daemon link, drawer — around the catalog and layout stores. The pure parts of four deleted modules moved next to the components that still use them: the spawn-form model and spawnSimilar into components/spawn, the transcript's markdown/format helpers and the diff into components/structured, the help overlay's grouping into components/dialogs. Document-level event names now live in uiEvents.ts. Deduplicated two pairs the migration left side by side: the sidebar uses dialogs/KillDialog and common/ContextMenu. It also listens for the command bar's kill/rename events, which nothing had picked up since catalog.ts went away. styles.css stays as the token and base layer, with `#app` renamed to `#root` and the pane-body rules re-keyed off the classes the React panes actually carry. Closes #253
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// Pure text helpers for the structured transcript, kept when
|
||||
// structuredRender.ts's imperative DOM builders were deleted (#253):
|
||||
// markdown rendering (sanitized) and the elapsed/context formatters.
|
||||
|
||||
import { marked, Renderer } from "marked";
|
||||
|
||||
import { escapeHtml } from "../../html.js";
|
||||
|
||||
/** Small inline renderer retained for compact labels and focused tests. */
|
||||
export function renderInlineMarkdown(text: string): string {
|
||||
const escaped = escapeHtml(text);
|
||||
return escaped
|
||||
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
||||
.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>")
|
||||
.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "<em>$1</em>");
|
||||
}
|
||||
|
||||
/** Render assistant output as GFM, then sanitize the complete result before it
|
||||
* enters the DOM. This gives transcripts proper tables, lists, code blocks and
|
||||
* links without trusting model-authored HTML or URL attributes. */
|
||||
export function renderMarkdown(text: string): string {
|
||||
const renderer = new Renderer();
|
||||
renderer.html = ({ text: raw }) => escapeHtml(raw);
|
||||
renderer.image = ({ text: alt }) => escapeHtml(alt ? `[image: ${alt}]` : "[image]");
|
||||
renderer.link = ({ href, title, tokens }) => {
|
||||
const label = renderer.parser.parseInline(tokens);
|
||||
const scheme = /^([a-z][a-z\d+.-]*):/i.exec(href.trim())?.[1]?.toLowerCase();
|
||||
if (scheme && !["http", "https", "mailto"].includes(scheme)) return label;
|
||||
const safeTitle = title ? ` title="${escapeHtml(title)}"` : "";
|
||||
return `<a href="${escapeHtml(href)}"${safeTitle}>${label}</a>`;
|
||||
};
|
||||
|
||||
return marked.parse(text, { async: false, gfm: true, renderer });
|
||||
}
|
||||
|
||||
function compactNumber(value: number): string {
|
||||
if (Math.abs(value) < 1_000) return String(value);
|
||||
if (Math.abs(value) < 1_000_000) return `${(value / 1_000).toFixed(value % 1_000 === 0 ? 0 : 1)}k`;
|
||||
return `${(value / 1_000_000).toFixed(value % 1_000_000 === 0 ? 0 : 1)}m`;
|
||||
}
|
||||
|
||||
export function formatElapsed(milliseconds: number): string {
|
||||
const seconds = Math.max(0, Math.floor(milliseconds / 1_000));
|
||||
if (seconds < 60) return `${seconds}s`;
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainder = seconds % 60;
|
||||
return remainder === 0 ? `${minutes}m` : `${minutes}m ${remainder}s`;
|
||||
}
|
||||
|
||||
export function formatContextTokens(value: number): string {
|
||||
return `${compactNumber(value)} context`;
|
||||
}
|
||||
Reference in New Issue
Block a user