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
9 changes: 4 additions & 5 deletions src/application/app-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
// Narrow scope (plan review): this service owns ONLY the persist half of
// each preference. Every write site except `toggleTheme` already mutates its
// own state field itself (splitters.ts sets `ctx.state.sidebarPx` before
// calling `ctx.save(...)`; dashboard.ts sets `state.dashLayout`/`dashCols`
// before calling `app.prefs.save(...)` directly — #276 Phase 5 deleted the
// flat `App.savePref` delegate; app.ts's `setResultRowLimit` sets
// calling `ctx.save(...)`; #276 Phase 5 deleted the flat `App.savePref`
// delegate; app.ts's `setResultRowLimit` sets
// `state.resultRowLimit` first) — so `save(name, value)` is a pure typed
// persist call, no state slice needed. `toggleTheme` is the one exception
// (issue ruling): the state flip AND the persist happen together here: the
Expand All @@ -29,7 +28,7 @@ import { KEYS } from '../state.js';
* untouched by this service. */
export type PreferenceKey =
| 'theme' | 'sidebarPx' | 'editorPct' | 'sideSplitPct' | 'cellDrawerPx'
| 'sidePanel' | 'resultRowLimit' | 'dashLayout' | 'dashCols'
| 'sidePanel' | 'resultRowLimit'
// #313 — the documentation pane's own persisted resize width, a sibling of
// cellDrawerPx (never shared with it — see splitters.ts's 'docPane' axis).
| 'docPanePx';
Expand All @@ -48,7 +47,7 @@ export interface AppPreferencesDeps {
export interface AppPreferences {
/** Generic persist-only setter — the exact `(name, value)` shape app.ts's
* former `App.savePref` delegate used to expose (#276 Phase 5 deleted it;
* dashboard.ts/saved-history.ts/splitters.ts's callers call `app.prefs.save`
* saved-history.ts/splitters.ts's callers call `app.prefs.save`
* directly now). This IS the service's write API: per-key typed setters
* were considered and dropped (review) — every real call site already
* holds a validated `{name, value}` pair, so a per-key surface would ship
Expand Down
60 changes: 0 additions & 60 deletions src/core/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,6 @@
// tiles stream through the shared `app.exec.executeRead` seam as of #193/#276, so the
// former `FORMAT JSON` → array-rows transform and its SQL prep were retired.)

/**
* Dashboard layout modes (#149 D2, #184): `arrange` = uniform multi-column grid
* (default, column count from `dashCols`), `report` = single centered column
* (1100px) with taller tiles, `wide` = one tile per row filling the full
* available dashboard width (#184, Grafana-style). Persisted per browser
* (`asb:dashLayout`); `wide` extends the key rather than migrating it, so
* existing arrange/report selections stay valid. The four *effective* views the
* UI exposes are derived by `activeDashboardView` below (wide, report, and
* arrange split into its 2- and 3-column cases).
*/
export const DASH_LAYOUTS: readonly string[] = ['arrange', 'report', 'wide'];

/** Snap a persisted layout to a known mode, defaulting to `arrange`. Pure. */
export function normalizeDashLayout(v?: string | null): string {
return v != null && DASH_LAYOUTS.includes(v) ? v : 'arrange';
}

/** Column-count options for Arrange mode (persisted `asb:dashCols`). */
export const DASH_COLS: readonly number[] = [2, 3];

/** Snap a persisted column count to 2 or 3, defaulting to 3. Pure. */
export function normalizeDashCols(n?: number | null): number {
return n != null && DASH_COLS.includes(n) ? n : 3;
}

// The two persisted keys `activeDashboardView`/`dashboardViewSelection` read
// and write — kept structural (rather than importing state.js's full State
// type) since only these two fields are ever touched here.
interface DashLayoutState {
dashLayout?: string;
dashCols?: number;
}

/**
* The four-way layout switcher's active value (#184), derived from the two
* persisted keys so a single control can drive them: `wide` and `report` map
* straight through; `arrange` splits into `columns-2`/`columns-3` by `dashCols`.
* Pure — the UI's segmented control reads this to mark exactly one button
* active, and `dashboardViewSelection` is its inverse (view → state changes).
*/
export function activeDashboardView(state: DashLayoutState): 'wide' | 'report' | 'columns-2' | 'columns-3' {
if (state.dashLayout === 'wide') return 'wide';
if (state.dashLayout === 'report') return 'report';
return state.dashCols === 2 ? 'columns-2' : 'columns-3';
}

/**
* Inverse of `activeDashboardView` (#184): the `{dashLayout, dashCols?}` a
* picked switcher value implies. `dashCols` is present only for the column
* views (the caller persists just the keys that actually changed, so choosing a
* column count never rewrites `dashLayout` when it is already `arrange`, and
* vice-versa). An unrecognized view falls back to the default `columns-3`.
*/
export function dashboardViewSelection(view?: string | null): DashLayoutState {
if (view === 'wide') return { dashLayout: 'wide' };
if (view === 'report') return { dashLayout: 'report' };
if (view === 'columns-2') return { dashLayout: 'arrange', dashCols: 2 };
return { dashLayout: 'arrange', dashCols: 3 };
}

/**
* Rows kept per dashboard tile (#149 D9). Preserves the 5000-point line/area
* chart cap (`CHART_ROW_CAPS` in `src/core/chart-data.js`) — a fetch cap below
Expand Down
3 changes: 0 additions & 3 deletions src/core/schema-cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,11 @@ function numberish(v: number | string | undefined): number | null {
* they belong in the detail drawer, not in card geometry. `comment` (trimmed,
* untruncated) isn't drawn as its own row — like the plain inline graph, it's a
* hover-only tooltip on the whole card, so it never affects the card's own layout.
* `_legacySkipIndexes` is never read — a bygone skip-index-rows argument some
* callers still pass; accepted and ignored so it can never affect the model.
*/
export function buildCardModel(
node?: CardGraphNode | null,
tableRow?: CardTableRow | null,
columns?: SchemaCardColumnRow[] | null,
_legacySkipIndexes?: unknown,
): CardModel {
const n = node || {};
const tr = tableRow || {};
Expand Down
3 changes: 0 additions & 3 deletions src/core/spec-draft.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
import { cloneJson } from './saved-query.js';
import {
createQuerySpecValidationService as _createQuerySpecValidationService,
// Kept for parity with the pre-conversion module — unused here, same as
// before (spec-schema.js's own export, consumed directly by other modules).
querySpecSchemaService,
} from './spec-schema.js';
import type { QuerySpecV1 } from '../generated/json-schema.types.js';
import { hasSameTimeRangeParameter } from './query-time-range.js';
Expand Down
7 changes: 2 additions & 5 deletions src/net/ch-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,6 @@ export async function exportQuery(ctx: ChCtx, sql: string, opts: ExportQueryOpti
* (e.g. multiquery SELECTs pass their own cap + session_id)
* @param onLine called per parsed stream object in streaming mode
* @param onChunk called once per read chunk in streaming mode
* @param onRaw unused by `runQuery` itself — the caller reads `.raw` off the
* returned result instead; kept for parity with the original docstring
*/
export interface RunQueryOptions {
format?: string;
Expand All @@ -903,7 +901,6 @@ export interface RunQueryOptions {
params?: Record<string, string | number>;
onLine?: (line: StreamLine) => void;
onChunk?: () => void;
onRaw?: (text: string) => void;
}

/** `runQuery`'s result: a query error, a raw-mode body, or a completed stream. */
Expand All @@ -916,12 +913,12 @@ export interface RunQueryResult {
/**
* Run a query in streaming mode (JSONStringsEachRowWithProgress) or raw mode
* (TSV/JSON). `onLine(parsedObj)` is called per stream object in streaming
* mode; `onRaw(text)` once for raw mode. Returns { error } or { raw } shape via
* mode. Returns { error } or { raw } shape via
* the result object the caller passes in `apply`.
*
* @param ctx
* @param sql
* @param o { format, signal, resultRowLimit, params, onLine(json), onChunk(), onRaw(text) }
* @param o { format, signal, resultRowLimit, params, onLine(json), onChunk() }
* `resultRowLimit` caps a normal result server-side (max_result_rows +
* result_overflow_mode); `params` are extra query-string options that ride
* alongside query_id (e.g. multiquery SELECTs pass their own cap + session_id).
Expand Down
10 changes: 0 additions & 10 deletions src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
} from './core/saved-query.js';
import type { QueryRoot } from './core/saved-query.js';
import { decodeStoredSavedQueries as decodeStoredSavedQueriesUntyped } from './core/library-codec.js';
import { normalizeDashLayout, normalizeDashCols } from './core/dashboard.js';
import {
loadJSON as loadJSONUntyped, saveJSON as saveJSONUntyped,
loadStr as loadStrUntyped,
Expand Down Expand Up @@ -351,8 +350,6 @@ export interface AppState {
theme: string;
density: string;
resultRowLimit: number;
dashLayout: string;
dashCols: number;
sidebarPx: number;
editorPct: number;
sideSplitPct: number;
Expand Down Expand Up @@ -495,8 +492,6 @@ export const KEYS = {
* on purpose: the concept it names is still live, it is not a Dashboard
* variable, and this is a persisted key besides. */
filterActive: 'asb:filterActive',
dashLayout: 'asb:dashLayout',
dashCols: 'asb:dashCols',
varRecent: 'asb:varRecent',
varRecentDisabled: 'asb:varRecentDisabled',
/** Isolated per-dashboard Dashboard-variable persistence (#303 Option B) — a
Expand Down Expand Up @@ -634,11 +629,6 @@ export function createState(read: StateReader = { loadJSON, loadStr }): AppState
// One persisted preference, default 500; a non-option stored value snaps
// back to the default so the selector always reflects a real choice.
resultRowLimit: normalizeRowLimit(parseInt(read.loadStr(KEYS.resultRowLimit, '500'), 10)),
// Dashboard layout prefs (#149 D2), persisted per browser. Plain (non-signal)
// like theme/density — the standalone dashboard page reads them at build time
// and mutates + re-saves on the Arrange/Report + column-count controls.
dashLayout: normalizeDashLayout(read.loadStr(KEYS.dashLayout, 'arrange')),
dashCols: normalizeDashCols(parseInt(read.loadStr(KEYS.dashCols, '3'), 10)),
sidebarPx: clamp(parseInt(read.loadStr(KEYS.sidebarPx, '248'), 10), 180, 420),
editorPct: num(KEYS.editorPct, 45, 15, 85),
sideSplitPct: num(KEYS.sideSplitPct, 58, 25, 85),
Expand Down
52 changes: 4 additions & 48 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ body {
the UA defaults converts "forgot to style a heading" from a visible bug into a
quiet inherit, and forces any real step up the ramp to be deliberate. Rules
that DO size a heading (.md-view, .modal-card, .schema-detail, .dash-notfound-title,
.docs-name, .detached-title, .section-label, .login-h1) all out-specify this. */
.docs-name, .detached-title, .section-label) all out-specify this. */
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: var(--fw-semibold);
Expand Down Expand Up @@ -456,8 +456,6 @@ h1, h2, h3, h4, h5, h6 {
.login-brand-text { display: flex; flex-direction: column; line-height: var(--lh-tight); }
.login-brand-name { font-size: var(--text-headline); font-weight: var(--fw-semibold); color: var(--fg); }
.login-brand-sub { font-size: var(--text-label); color: var(--fg-faint); }
.login-h1 { font-size: var(--text-doc-h1); font-weight: var(--fw-semibold); letter-spacing: -.3px; color: var(--fg); margin-bottom: 4px; }
.login-sub { font-size: var(--text-body); color: var(--fg-mute); margin-bottom: 20px; line-height: var(--lh-body); }

/* SSO section */
.login-sso { display: flex; flex-direction: column; gap: 8px; }
Expand Down Expand Up @@ -579,13 +577,6 @@ h1, h2, h3, h4, h5, h6 {
flex-shrink: 0;
}
.logo-name { font-size: var(--text-body); font-weight: var(--fw-semibold); color: var(--fg); }
.env-chip {
font-size: var(--text-label); color: var(--fg-faint);
padding: 2px 6px;
background: var(--bg-chip);
border-radius: var(--r-sm);
font-family: var(--mono);
}
.conn-status {
display: flex; align-items: center; gap: 6px;
font-size: var(--text-label); color: var(--fg-mute);
Expand All @@ -607,11 +598,6 @@ h1, h2, h3, h4, h5, h6 {
flex-shrink: 0;
}
.conn-status.dim::before { background: var(--fg-faint); box-shadow: none; }
.user-email {
font-size: var(--text-label); color: var(--fg-mute);
font-family: var(--mono);
max-width: 220px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.hd-btn {
width: 26px; height: 26px;
border: none; background: transparent;
Expand Down Expand Up @@ -659,7 +645,6 @@ h1, h2, h3, h4, h5, h6 {
}

/* ------------ header File menu + library title ------------ */
.hd-divider { width: 1px; height: 18px; background: var(--border); flex-shrink: 0; }
.hd-file-btn {
display: flex; align-items: center; gap: 5px; height: 26px; padding: 0 8px;
border: none; border-radius: var(--r-sm); background: transparent; color: var(--fg-mute);
Expand Down Expand Up @@ -733,10 +718,6 @@ h1, h2, h3, h4, h5, h6 {
margin-top: 4px; padding: 8px 12px; border-top: 1px solid var(--border-faint);
font-size: var(--text-micro); color: var(--fg-faint); font-family: var(--mono);
}
/* Variable history (#171): a `.fm-item`-styled <label> so the whole row
toggles the checkbox, matching the rest of the File menu's row rhythm. */
.fm-toggle { cursor: pointer; }
.fm-checkbox { accent-color: var(--accent); flex-shrink: 0; cursor: pointer; }
/* Replace / New confirm dialog */
.fm-dialog-backdrop {
position: fixed; inset: 0; z-index: 130; background: var(--scrim);
Expand All @@ -751,7 +732,6 @@ h1, h2, h3, h4, h5, h6 {
.fm-dialog-title { font-size: var(--text-headline); font-weight: var(--fw-semibold); color: var(--fg); margin-bottom: 6px; }
.fm-dialog-body { font-size: var(--text-body); color: var(--fg-mute); line-height: var(--lh-body); margin-bottom: 18px; }
.fm-dialog-body b { color: var(--fg); }
.fm-mono { color: var(--fg); font-family: var(--mono); }
.fm-dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
.fm-dialog-cancel, .fm-dialog-confirm {
height: 30px; padding: 0 14px; border-radius: var(--r-sm); font-family: inherit;
Expand Down Expand Up @@ -967,21 +947,6 @@ h1, h2, h3, h4, h5, h6 {
.saved-row:hover .sv-act { display: inline-flex; }
.sv-act:hover { color: var(--fg); background: var(--bg-hover); }
.side-count { color: var(--fg-faint); font-weight: var(--fw-regular); }
/* Export/Import row at the end of the Saved panel. margin-top:auto sinks it to
the bottom when the list is short, but it scrolls away with a long list (not
sticky) — no need to keep it on screen once there's plenty to scroll. */
.saved-actions {
margin-top: auto; flex-shrink: 0;
display: flex; gap: 6px; padding: 6px 10px;
border-top: 1px solid var(--border); background: var(--bg-editor);
}
.sv-io {
flex: 1; height: 24px; border: 1px solid var(--border); border-radius: var(--r-sm);
background: transparent; color: var(--fg-mute); font-size: var(--text-label); font-family: inherit;
cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 5px;
}
.sv-io:hover:not([disabled]) { background: var(--bg-hover); color: var(--fg); }
.sv-io[disabled] { opacity: .45; cursor: not-allowed; }
.history-row {
position: relative;
padding: 8px 10px; cursor: pointer; user-select: none;
Expand Down Expand Up @@ -2073,13 +2038,7 @@ body.detached-tab .graph-overlay-panel {
box-shadow: var(--ring-error);
}

/* #360 source-backed Dashboard variable transport states. The field is also
`disabled`; these mark WHY, so a benign 'waiting' (dashed, with a
"Waiting for: …" note) or a 'stale'/refreshing field (dimmed) reads
differently from a real 'error' (red) at a glance. */
.var-input.is-error { border-color: var(--error-bd); background: var(--error-bg); }
.var-input.is-waiting { border-style: dashed; }
.var-input.is-stale { opacity: 0.55; font-style: italic; }
.var-field-note { grid-column: 2; font-size: var(--text-label); color: var(--fg-mute); white-space: nowrap; }
/* Relative-time preset combobox (#169): the accessible dropdown-on-focus +
type-to-filter control for date-like {name:Type} variables (#174 §1) —
Expand Down Expand Up @@ -2190,8 +2149,8 @@ body.detached-tab .graph-overlay-panel {
field; the popover is its own `position:fixed` panel, the same
escape-the-scrolling-strip trick as .var-combo-list/.file-menu.
#447 deleted this block with the curated filter model; it came back with the
control, minus the per-field source-status rules (.is-waiting/.is-stale) that
belonged to the Filter-source machine rather than to the control. */
control, without the Filter-source state machine that belonged to the prior
curated-filter implementation. */
.ms-field { grid-column: 2; display: inline-flex; }
.ms-trigger {
display: inline-flex; align-items: center;
Expand Down Expand Up @@ -2760,7 +2719,6 @@ table.res-table tbody tr:hover td.idx { background: var(--bg-hover); }
.docs-categories { display: flex; flex-wrap: wrap; gap: 6px; }
.docs-chip { font-size: var(--text-label); padding: 3px 9px; border-radius: var(--r-pill); background: var(--bg-chip); color: var(--fg-mute); }
.docs-field-label { font-size: var(--text-label); font-weight: var(--fw-semibold); text-transform: uppercase; letter-spacing: .03em; color: var(--fg-faint); margin-bottom: 4px; }
.docs-field-text { font-size: var(--text-body); color: var(--fg); white-space: pre-wrap; word-break: break-word; }
.docs-flags { display: flex; gap: 6px; flex-wrap: wrap; }
.docs-example-code { border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
/* #314 phase-2 pane elements */
Expand Down Expand Up @@ -3031,11 +2989,9 @@ table.res-table tbody tr:hover td.idx { background: var(--bg-hover); }
concern (side-by-side suits wide-but-short landscape). The hidden items are
all decorative or desktop-only; File / theme / user menu remain. */
@media (max-width: 900px) {
.logo-name, .env-chip, .app-header .hd-divider, .hd-hide-mobile { display: none; }
.logo-name, .hd-hide-mobile { display: none; }
.connection-chip .connection-host { display: none; }
.connection-chip .connection-state { display: inline; }
/* Legacy header controls may still opt into hiding a nonessential label. */
.hd-hide-mobile-label { display: none; }
/* Cap the user-menu label so a long local-part (`user ▾`) can't widen the
header back past the viewport. */
.hd-btn.user-btn .user-short { max-width: 90px; }
Expand Down
5 changes: 2 additions & 3 deletions src/ui/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,8 @@ export function createApp(env: CreateAppEnv = {}): App {

// --- persistence -------------------------------------------------------
// The true-preference persist service (#276 Phase 4D) — theme/sidebarPx/
// editorPct/sideSplitPct/cellDrawerPx/sidePanel/resultRowLimit/dashLayout/
// dashCols, constructible without App/AppState/DOM. Consumers
// (dashboard.ts/saved-history.ts/splitters.ts) call `app.prefs.save(name,
// editorPct/sideSplitPct/cellDrawerPx/sidePanel/resultRowLimit, constructible
// without App/AppState/DOM. Consumers (saved-history.ts/splitters.ts) call `app.prefs.save(name,
// value)` directly (#276 Phase 5 deleted the flat `App.savePref` delegate);
// `toggleTheme` below composes `prefs.toggleTheme()` (the state-flip +
// persist) with its own DOM half.
Expand Down
2 changes: 1 addition & 1 deletion src/ui/workbench/workbench-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ export function mountWorkbenchShell(deps: WorkbenchShellDeps): () => void {
e.preventDefault();
try { actions.showSchemaGraph(JSON.parse(payload)); } catch { /* malformed payload */ }
});
app.dom.editorResultsSplit = h('div', { class: 'row-resize', onmousedown: (e: DragStartEvent) => doStartDrag(e, 'row', dragCtx) });
app.dom.editorResultsSplit = h('div', { class: 'row-resize editor-results-split', onmousedown: (e: DragStartEvent) => doStartDrag(e, 'row', dragCtx) });

const workbenchEl = h('div', { class: 'workbench' }, qtabsRow, editorToolbar, app.dom.varStrip, app.dom.editorRegion, app.dom.editorResultsSplit, app.dom.resultsRegion);
queryHost.appendChild(workbenchEl);
Expand Down
3 changes: 0 additions & 3 deletions tests/e2e/dashboard-mobile.html
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,6 @@
timeRegion.append(trHeading, trField.el, trSep);
timeHost.append(timeRegion);

window.__prefs = { dashLayout: 'report', dashCols: 3 };
localStorage.setItem('asb:dashLayout', 'report');
localStorage.setItem('asb:dashCols', '3');
window.__layoutApplyCount = 0;
window.__setLayout = (mode) => {
// #321: 'full-width'/'wide' removed — every mode this harness exercises
Expand Down
Loading