/** * Shared presentational helpers for the research workbench views: the stage * label map, failure-copy translation, byte-size and run-duration formatting, * the figure route * URL builder, the experiments comparison-chart helpers (numeric metric * keys, chart rows, bar widths, value formatting), the library tag * collection/filter helpers, the server probe stage inference * ({@link probeStageOf}), and the figure-upload drop filter * ({@link filterDropFiles}). No JSX, no subscriptions. * @module dsh-client-ui-mimir/client/view-common */ import type { BibEntry, ExperimentRecord, ExperimentStatus, MetricDirection, OutlineNode, PaperRecord, ProjectStage, SectionMove, SectionOutlineTitles, ServerView, SubsectionMove } from 'dsh-mimir/types'; import type { ResearchFailureView, ResearchSaveState } from './controller.ts'; import type { ResearchKey } from './locales.ts'; /** The `t` function shape every view receives from the panel. */ export type ResearchT = (key: ResearchKey, params?: Record) => string; /** Locale key of one autosave state label (the editor's and the bib panel's pill). */ export declare const SAVE_KEYS: Record; /** Locale key of one pipeline stage label. */ export declare const STAGE_KEYS: Record; /** Pipeline stages in order (the overview progress bar). */ export declare const STAGES: readonly ProjectStage[]; /** * Locale key of one host failure code. Codes absent here carry a * context-bearing `message` from the host (the offending path, the parse * detail); those fall through to that message rather than losing it behind a * generic line. */ export declare const FAILURE_CODE_KEYS: Record; /** * Localized copy for one failure: a mapped code renders its dedicated string, * an unmapped one falls back to the host message, and a code that arrives * with neither (an unmapped code whose message is empty) renders the generic * line so the UI never shows an empty error. */ export declare function failureCopy(t: ResearchT, failure: ResearchFailureView | null): string; /** Human-readable byte size (B/KB/MB, one decimal above 1 KB). */ export declare function formatSize(sizeBytes: number): string; /** * Character range of one 1-based line in one text (the issue/outline * click-to-jump selection). Offsets count newlines; the last line runs to the * text's end. Returns null for a line past the end or below 1. */ export declare function lineRangeOf(text: string, line: number): { readonly start: number; readonly end: number; } | null; /** Localized relative timestamp (e.g. a probe's checkedAt), coarse-grained. */ export declare function relativeTime(t: ResearchT, iso: string): string; /** Human-readable run duration (a job's wall-clock time): `900ms`, `12.3s`, `3.2min`. */ export declare function formatDurationMs(durationMs: number): string; /** * Build the figure route URL for one file of one project's paper directory. * The record's `paperDir` rides along as `?dir=`, same convention as the * PDF preview URL. */ export declare function figureUrl(projectId: string, relPath: string, dir: string | undefined): string; /** * Build the meeting-deck download URL for one generated pptx. The route * answers with `Content-Disposition: attachment`, so an `` download * works without an RPC round-trip. */ export declare function meetingDeckUrl(projectId: string, file: string): string; /** One run's row in one metric's comparison chart. */ export interface MetricChartRow { readonly id: string; readonly name: string; readonly status: ExperimentStatus; readonly value: number; } /** * Metric keys shared as finite numbers by at least two experiments — the keys * worth a comparison chart (a key only one run carries has nothing to compare * against). Sorted alphabetically so the chart grid is stable across renders. */ export declare function numericMetricKeys(experiments: readonly ExperimentRecord[]): string[]; /** * Rows of one metric's comparison chart: every run carrying a finite number * for the key, oldest first (updatedAt order) so the bars read as a trend. */ export declare function metricChartRows(experiments: readonly ExperimentRecord[], key: string): MetricChartRow[]; /** One bar's geometry in a zero-baseline chart: percentages of the bar lane. */ export interface BarSpan { /** Left edge of the bar (0–100). */ readonly leftPct: number; /** Width of the bar (0–100). */ readonly widthPct: number; /** True for a negative value: the bar extends LEFT of the zero line. */ readonly negative: boolean; } /** * Bar geometry (percentages) for one chart's values on a ZERO baseline * (#220): the lane spans [min(0, values), max(0, values)], so negative bars * extend left of the zero line and positive bars right. An all-zero chart * yields zero-width bars. Both the panel chart and the exported SVG figure * consume this one rule, so they can never disagree. */ export declare function barSpans(values: readonly number[]): BarSpan[]; /** * Zero-line position (percent of the bar lane) for one chart's values, or * null when every value is non-negative (the lane's left edge IS the zero * line, which both renderers already draw). */ export declare function zeroLinePct(values: readonly number[]): number | null; /** * Index of a chart's best run under the metric's direction (#220): the * smallest value for `min`, the largest for `max`, and null for `none` * (unordered metric — no best exists). Ties keep the earliest row. */ export declare function bestRowIndex(rows: readonly MetricChartRow[], direction: MetricDirection): number | null; /** * Per-metric directions merged across one project's experiments (#220): * later-updated records win, so the newest run's form edits govern the * chart. Records predating the field contribute nothing. */ export declare function metricDirectionsOf(experiments: readonly ExperimentRecord[]): Record; /** * One run name wrapped to at most two chart-label lines so the bar charts stay * readable without ellipsizing mid-word. The wrap budget is in half-width * units (see {@link CHART_LABEL_LINE_UNITS}); the first line fills greedily * and breaks at the last colon or space inside the budget, and a name still * too long ellipsizes its second line (the full name rides the SVG ``). */ export declare function chartNameLines(name: string): readonly [string] | readonly [string, string]; /** * Compact display form of one metric value: strings pass through, integers * print as-is, other numbers keep at most four significant digits. */ export declare function formatMetricValue(value: number | string): string; /** * Bibliography fields the bib panel edits through dedicated form inputs, in * display order; every other field of the entry rides the raw name/value rows. */ export declare const COMMON_BIB_FIELDS: readonly ["title", "author", "year", "journal", "booktitle", "eprint", "archiveprefix", "url", "note"]; /** One raw name/value field row of the bib entry editor. */ export interface BibFieldDraft { name: string; value: string; } /** The bib entry editor's draft: citation key, entry type, the common-field inputs, and the raw rows. */ export interface BibEntryDraft { key: string; type: string; /** Common-field input values keyed by field name (absent names read as ''). */ common: Record<string, string>; extra: BibFieldDraft[]; } /** Open the editor on one entry: common fields into their inputs, the rest as raw rows. */ export declare function bibDraftFromEntry(entry: BibEntry): BibEntryDraft; /** * Assemble the entry one draft saves to: key and type trimmed (an empty key * or type rejects with null — the panel shows its own validation copy), empty * common inputs dropped, empty-name or empty-value raw rows dropped, raw row * names lowercased, and a raw row naming a common field overriding the form * input (last write wins, matching the parser's field-merge rule). */ export declare function bibEntryFromDraft(draft: BibEntryDraft): BibEntry | null; /** * Build the paper-PDF route URL of one remembered paper (the library card's * embedded reader). `version` cache-busts a refetch: the route serves the * same path after every fetch, and the no-cache reply alone does not force an * already-open iframe to re-request. */ export declare function paperPdfUrl(arxivId: string, version: number): string; /** * One-line summary of one bibliography entry (the bib panel's row): the * title when present, else the author/year pair, else the entry type. * Whitespace runs collapse; the result truncates at 80 characters. */ export declare function bibSummaryOf(entry: BibEntry): string; /** * Translate one outline drop into a section move. `insertAt` is the insertion * indicator's index in the CURRENT top-level order (0..titles.length); the * move's `targetIndex` addresses the order after the dragged section is * removed. Returns null for a no-op drop (back onto its own slot) or an * unknown title. */ export declare function sectionMoveFromDrop(titles: readonly string[], title: string, insertAt: number): SectionMove | null; /** * The conflict-check snapshot of one parsed outline: the top-level section * titles plus each section's direct child titles, in document order. This is * the `baseOutline` a subsection reorder commits against. */ export declare function outlineSectionTitles(nodes: readonly OutlineNode[]): SectionOutlineTitles[]; /** The dragged subsection of one drop: its current section title and its own title. */ export interface SubsectionDrag { readonly sectionTitle: string; readonly title: string; } /** * Translate one subsection drop into a subsection move. `insertAt` is the * insertion indicator's index in the CURRENT subsection order of the target * section (0..count); for a same-section drop the move's `targetIndex` * addresses the order after the dragged subsection is removed, mirroring * {@link sectionMoveFromDrop}. Returns null for a no-op drop (back onto its * own slot) or an unknown section/subsection. */ export declare function subsectionMoveFromDrop(nodes: readonly OutlineNode[], drag: SubsectionDrag, targetSectionTitle: string, insertAt: number): SubsectionMove | null; /** All tags across the library, deduped and alphabetically sorted (the filter bar). */ export declare function collectTags(papers: readonly PaperRecord[]): string[]; /** All tags across the server list, deduped and alphabetically sorted (the filter bar). */ export declare function collectServerTags(servers: readonly ServerView[]): string[]; /** One stage of the server probe pipeline (mirrors `ServerProbeStage` of dsh-mimir). */ export type ProbeStage = 'tcp' | 'ssh' | 'gpu'; /** End of the TCP stage's time window in ms (the host's TCP probe timeout). */ export declare const PROBE_TCP_WINDOW_MS = 4000; /** * End of the SSH stage's time window in ms: the TCP budget plus the ssh * connect timeout (4s + 5s). Past this the probe is reading the GPU table. */ export declare const PROBE_SSH_WINDOW_MS = 9000; /** * The probe stage the panel should DISPLAY after `elapsedMs` of an in-flight * probe, derived from the host's per-stage timeouts (pure client-side * inference — the host only reports the stage once the probe settles). */ export declare function probeStageOf(elapsedMs: number): ProbeStage; /** Locale key of one in-flight probe stage's progress label. */ export declare const PROBE_STAGE_KEYS: Record<ProbeStage, ResearchKey>; /** Locale key of one settled probe's per-stage failure label (the host's `stage`). */ export declare const PROBE_FAILURE_KEYS: Record<ProbeStage, ResearchKey>; /** Filter the server list by one active tag; a null selector passes everything. */ export declare function filterServers(servers: readonly ServerView[], tag: string | null): ServerView[]; /** * Filter the library by one active tag and/or one linked project; a null * selector passes everything on its axis. */ export declare function filterPapers(papers: readonly PaperRecord[], tag: string | null, projectId: string | null): PaperRecord[]; /** * Extract a bare arXiv id from one web result URL: an `arxiv.org/abs/<id>` * (or `arxiv.org/pdf/<id>`) path, version suffix allowed. Returns null for * every other URL — the bridge that lets a web result import into the wiki. * @param url - the result URL, verbatim from the engine. */ export declare function arxivIdFromUrl(url: string): string | null; /** Extensions the figure upload accepts, shared by the file input and the drop filter. */ export declare const FIGURE_ACCEPT_EXTENSIONS: readonly [".png", ".jpg", ".jpeg", ".svg", ".pdf"]; /** The result of splitting dragged files by the accept list. */ export interface DropFileFilter<T> { readonly accepted: T[]; readonly rejected: T[]; } /** * Split dragged files into those matching the accept list (by extension, * case-insensitive) and those rejected, so the figures view uploads the * accepted ones and reports the rest instead of silently dropping them. * Generic over anything carrying a `name` so it stays DOM-free and testable. */ export declare function filterDropFiles<T extends { readonly name: string; }>(files: readonly T[], accept: readonly string[]): DropFileFilter<T>; //# sourceMappingURL=view-common.d.ts.map