diff --git a/.claude/rules/log-viewer.md b/.claude/rules/log-viewer.md index ef53aead..18776808 100644 --- a/.claude/rules/log-viewer.md +++ b/.claude/rules/log-viewer.md @@ -5,68 +5,45 @@ paths: # log-viewer rules -Webview UI. Applies when working under `log-viewer/`. +Webview UI. ## Boundary -- MUST NOT import `vscode` or anything from `lana/`. -- Communicate with the extension via message passing only. +- Never import `vscode` or anything from `lana/`. Message passing only. -## Performance budgets +## Performance -- Parse + render: `<5MB` → `<1s`, `10MB` → `<3s`, `20MB+` → `<5s`. -- No synchronous operations >50ms blocking the extension host. -- Operations >100ms show a progress indicator. -- Benchmark against large logs from `sample-app/`. +- Parse + render: `<5MB` in `<1s`, `10MB` in `<3s`, `20MB+` in `<5s`. Benchmark on `sample-app/` logs. +- Nothing synchronous over 50ms. Show progress over 100ms. -## Theme changes +## Theme -- The panel keeps its context and is never re-created, so a theme switch has to be observed at - runtime. HTML re-themes itself through `--vscode-*`; anything drawn on a canvas does not. - Canvas code reads its colours through `themeObserver.on(…)` and repaints, and must never - re-initialise the renderer to do it. +- The panel is never re-created, so a theme switch is observed at runtime. HTML re-themes through + `--vscode-*`; canvas does not — read colours via `themeObserver.on(…)` and repaint, never + re-initialise the renderer. +- Check every change in a light and a dark theme. -## UI appearance +## Appearance -- **Never write a raw literal for a color, radius, space, shadow or border width.** Use a - `--lana-*` token, or the `--vscode-*` var the value comes from. Replace the literals in the code - you touch; older files still hold them and we convert file by file. -- **A `--lana-*` token when the value is more than one var, and the plain `--vscode-*` var when it - is not.** Give a value a token when any of these holds: - 1. the role appears in three or more files; - 2. VS Code has no var for it, so it is a scale or a role of ours (`--lana-space-*`, - `--lana-pane-min`); - 3. the value is a chain, a `calc` or a `color-mix`, not a single var; - 4. the var is absent in a VS Code we support, so a fallback is needed — `cornerRadius-*`, - `spacing-size*` and `strokeThickness` ship in no stable release. - - Otherwise use the var directly. A token wrapping one already-semantic var used in one file - (`--vscode-editorCursor-foreground`) only adds a name to learn and a hop to trace. - -- **Put a new token in `styles/tokens.css` as `var(--vscode-…, )`.** The literal is what a - host outside VS Code falls back to, and what covers a var VS Code has yet to register. -- **Never define or override a `--vscode-*` name.** An override is global to the webview, so - re-skinning one role changes every other consumer of that var, and shadowing a platform name - leaves no way to tell what the var means. A host outside VS Code supplies the whole `--vscode-*` - block once instead. The one exception is skinning a `vscode-elements` component, which reads its - own `--vscode-*` names: scope the override to that element, never to `:host` or `:root`. -- **Data palettes stay literal.** The timeline categories (`timeline/themes/Themes.ts`) and the - metric-strip tiers (`metric-strip/metric-strip-colors.ts`) show meaning, not chrome, so they do - not follow the host theme. -- **A component that names a token must also carry the tokens.** `globalStyles` carries them, so - `static styles = [globalStyles, …]` is enough. Without `globalStyles`, add `tokenStyles` - (`styles/tokens.styles.ts`). The document copy, which the build injects, styles only the popups - that tabulator puts in `document.body`. -- **Check each UI change in a light theme and in a dark theme.** +- Write no literal colour, radius, space, shadow or border width. Use a `--lana-*` token, or the + `--vscode-*` var the value comes from, and convert the literals in the code you touch. +- Make it a token when the role is in three or more files, is ours (`--lana-space-*`, + `--lana-pane-min`), is a chain, `calc` or `color-mix`, or the var ships in no stable VS Code + (`cornerRadius-*`, `spacing-size*`, `strokeThickness`). Otherwise use the var directly. +- New tokens go in `styles/tokens.css` as `var(--vscode-…, )`; the literal is what a host + outside VS Code gets. Never end a chain in `transparent` — the value is then defined but invisible, + and no consumer fallback can fire. +- Never define or override a `--vscode-*` name — an override is global to the webview. Exception: + skinning a `vscode-elements` component; scope it to that element, never `:host` or `:root`. +- Data palettes stay literal: they show meaning, not chrome (`timeline/themes/Themes.ts`, + `metric-strip/metric-strip-colors.ts`). +- A component naming a token must carry the tokens: `globalStyles`, or `tokenStyles` + (`styles/tokens.styles.ts`) without it. The document copy styles only tabulator's body popups. ## Key paths -- Timeline: `log-viewer/src/features/timeline/` -- Parser: `log-viewer/src/core/log-parser/` -- Theme observer: `log-viewer/src/core/theme/ThemeObserver.ts` -- Tokens: `log-viewer/src/styles/tokens.css` +`features/timeline/` · `core/log-parser/` · `core/theme/ThemeObserver.ts` · `styles/tokens.css` ## Testing -- Features and bug fixes include tests. -- Breaking changes to log parsing cover both old and new formats. +- Features and fixes ship with tests. Parser format changes cover the old and the new format. diff --git a/AGENTS.md b/AGENTS.md index 7563fe14..a6bf633f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,7 @@ the TS ≤6.0 API (lands in TS 7.1). Don't remove until typescript-eslint suppor - **Performance** — handle large logs (50MB+, 500k+ lines) without blocking the UI. - **UX** — discoverable, accessible, actionable errors. - **Testing** — features and bug fixes ship with tests; CI blocks failures. +- **Comments** — only what the code cannot say, one short line, and only where needed. ## Critical boundary diff --git a/log-viewer/src/components/CallTreeDetail.ts b/log-viewer/src/components/CallTreeDetail.ts index e2f05a9e..00fafb94 100644 --- a/log-viewer/src/components/CallTreeDetail.ts +++ b/log-viewer/src/components/CallTreeDetail.ts @@ -1,6 +1,7 @@ /* * Copyright (c) 2026 Certinia Inc. All rights reserved. */ +import { consume } from '@lit/context'; import type { LogEvent } from 'apex-log-parser'; import { LitElement, css, html, unsafeCSS, type PropertyValues } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; @@ -12,7 +13,8 @@ import { } from 'tabulator-tables'; import { eventBus } from '../core/events/EventBus.js'; -import { LogLoadedController } from '../core/events/LogLoadedController.js'; +import { logContext } from '../core/log/logContext.js'; +import type { LogStore } from '../core/log/LogStore.js'; import { formatDuration, formatInteger } from '../core/utility/Util.js'; import { commonColumnDefaults, @@ -186,16 +188,10 @@ export class CallTreeDetail extends LitElement { 'bottom-up': null, }; - // A whole-log tree can mount before the first parse finishes (the scoped - // tree cannot — a selection implies a parsed log), so rebuild when the log - // lands. - private readonly _logLoaded = new LogLoadedController(this, () => { - if (!this.wholeLog) { - return; - } - this._invalidateScope(); - void this._showActive(); - }); + /** The log on screen, from the app root. */ + @consume({ context: logContext, subscribe: true }) + @property({ attribute: false }) + logStore: LogStore | null = null; constructor() { super(); @@ -316,7 +312,11 @@ export class CallTreeDetail extends LitElement { ]; updated(changed: PropertyValues) { - const scopeChanged = changed.has('eventIndex') || changed.has('instances'); + // Only the whole-log tree can mount before a parse, so there a new log is a new scope. + const scopeChanged = + (changed.has('logStore') && this.wholeLog) || + changed.has('eventIndex') || + changed.has('instances'); if (scopeChanged) { this._invalidateScope(); } diff --git a/log-viewer/src/components/HotPath.ts b/log-viewer/src/components/HotPath.ts index e29177ae..48d2570a 100644 --- a/log-viewer/src/components/HotPath.ts +++ b/log-viewer/src/components/HotPath.ts @@ -1,15 +1,17 @@ /* * Copyright (c) 2026 Certinia Inc. All rights reserved. */ +import { consume } from '@lit/context'; import { LitElement, css, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; +import { customElement, property } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import '#vscode-elements/vscode-icon.js'; -import { LogLoadedController } from '../core/events/LogLoadedController.js'; +import { logContext } from '../core/log/logContext.js'; +import type { LogStore } from '../core/log/LogStore.js'; import { formatDuration } from '../core/utility/Util.js'; import { - getCurrentExecutionHighlights, + getExecutionHighlights, type ExecutionHighlights, type HotPathFrame, } from '../features/call-tree/utils/ExecutionHighlights.js'; @@ -31,8 +33,11 @@ const FRAME_CAP = 10; */ @customElement('hot-path') export class HotPath extends LitElement { - /** The path has to follow the log itself. */ - private readonly _logLoaded = new LogLoadedController(this); + /** The log on screen, from the app root. */ + @consume({ context: logContext, subscribe: true }) + @property({ attribute: false }) + logStore: LogStore | null = null; + private readonly _palette = new CategoryPaletteController(this); static styles = [ @@ -69,7 +74,8 @@ export class HotPath extends LitElement { ]; render() { - const highlights = getCurrentExecutionHighlights(); + const log = this.logStore?.log; + const highlights = log && getExecutionHighlights(log); if (!highlights || !highlights.hotPath.length) { return html`

The log has no timed calls.

`; } diff --git a/log-viewer/src/components/HotSpots.ts b/log-viewer/src/components/HotSpots.ts index c337b049..673a3131 100644 --- a/log-viewer/src/components/HotSpots.ts +++ b/log-viewer/src/components/HotSpots.ts @@ -1,14 +1,16 @@ /* * Copyright (c) 2026 Certinia Inc. All rights reserved. */ +import { consume } from '@lit/context'; import { LitElement, html } from 'lit'; -import { customElement } from 'lit/decorators.js'; +import { customElement, property } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; -import { LogLoadedController } from '../core/events/LogLoadedController.js'; +import { logContext } from '../core/log/logContext.js'; +import type { LogStore } from '../core/log/LogStore.js'; import { formatDuration } from '../core/utility/Util.js'; import { - getCurrentExecutionHighlights, + getExecutionHighlights, type HotSpotRow, } from '../features/call-tree/utils/ExecutionHighlights.js'; import { globalStyles } from '../styles/global.styles.js'; @@ -25,14 +27,18 @@ import { dispatchInspectorReveal } from './inspectorReveal.js'; */ @customElement('hot-spots') export class HotSpots extends LitElement { - /** The list has to follow the log itself. */ - private readonly _logLoaded = new LogLoadedController(this); + /** The log on screen, from the app root. */ + @consume({ context: logContext, subscribe: true }) + @property({ attribute: false }) + logStore: LogStore | null = null; + private readonly _palette = new CategoryPaletteController(this); static styles = [globalStyles, inspectorSectionStyles, revealRowStyles]; render() { - const highlights = getCurrentExecutionHighlights(); + const log = this.logStore?.log; + const highlights = log && getExecutionHighlights(log); if (!highlights || !highlights.hotSpots.length) { return html`

The log has no timed calls.

`; } diff --git a/log-viewer/src/components/__tests__/HotPath.test.ts b/log-viewer/src/components/__tests__/HotPath.test.ts index 522544f5..b44b059f 100644 --- a/log-viewer/src/components/__tests__/HotPath.test.ts +++ b/log-viewer/src/components/__tests__/HotPath.test.ts @@ -5,13 +5,14 @@ */ import { beforeEach, describe, expect, it } from '@jest/globals'; +import type { LogStore } from '../../core/log/LogStore.js'; import type { ExecutionHighlights } from '../../features/call-tree/utils/ExecutionHighlights.js'; jest.mock('#vscode-elements/vscode-icon.js', () => ({})); let highlights: ExecutionHighlights | null = null; jest.mock('../../features/call-tree/utils/ExecutionHighlights.js', () => ({ - getCurrentExecutionHighlights: () => highlights, + getExecutionHighlights: () => highlights, })); import '../HotPath.js'; @@ -35,6 +36,7 @@ const pathOf = (frameCount: number): ExecutionHighlights => ({ const hotPath = async () => { const element = document.createElement('hot-path'); + element.logStore = { log: {} } as unknown as LogStore; document.body.append(element); await element.updateComplete; return element; diff --git a/log-viewer/src/components/__tests__/HotSpots.test.ts b/log-viewer/src/components/__tests__/HotSpots.test.ts index e59c823b..14915021 100644 --- a/log-viewer/src/components/__tests__/HotSpots.test.ts +++ b/log-viewer/src/components/__tests__/HotSpots.test.ts @@ -5,11 +5,12 @@ */ import { beforeEach, describe, expect, it } from '@jest/globals'; +import type { LogStore } from '../../core/log/LogStore.js'; import type { ExecutionHighlights } from '../../features/call-tree/utils/ExecutionHighlights.js'; let highlights: ExecutionHighlights | null = null; jest.mock('../../features/call-tree/utils/ExecutionHighlights.js', () => ({ - getCurrentExecutionHighlights: () => highlights, + getExecutionHighlights: () => highlights, })); import '../HotSpots.js'; @@ -32,6 +33,7 @@ const spotsOf = (): ExecutionHighlights => ({ const hotSpots = async () => { const element = document.createElement('hot-spots'); + element.logStore = { log: {} } as unknown as LogStore; document.body.append(element); await element.updateComplete; return element; diff --git a/log-viewer/src/core/events/EventBus.ts b/log-viewer/src/core/events/EventBus.ts index 1c287323..a644041a 100644 --- a/log-viewer/src/core/events/EventBus.ts +++ b/log-viewer/src/core/events/EventBus.ts @@ -32,12 +32,6 @@ export type DetailSelection = | { kind: 'aggregate'; instances: number[]; label: string }; interface EventMap { - // A log finished parsing and the event-lookup service holds it. Whole-log - // content reads that service directly, so it needs telling once the data is - // there; the first paint happens before any log exists. No payload: a listener - // that needs the log asks the service for it, keeping one source of truth. - 'log:loaded': Record; - // Supply eventIndex (preferred — unique) OR timestamp (fallback for raw-log entry where eventIndex isn't known). 'timeline:navigate-to': diff --git a/log-viewer/src/core/events/LogLoadedController.ts b/log-viewer/src/core/events/LogLoadedController.ts deleted file mode 100644 index 23b473f6..00000000 --- a/log-viewer/src/core/events/LogLoadedController.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2026 Certinia Inc. All rights reserved. - */ -import type { ReactiveController, ReactiveControllerHost } from 'lit'; - -import { eventBus } from './EventBus.js'; - -/** - * Re-runs `onLoad` (default: a host re-render) whenever a log finishes parsing. - * The inspector's sections paint before the first parse and rebuild only on a - * tab change or a selection, so a section that reads the log has to follow the - * log itself. - */ -export class LogLoadedController implements ReactiveController { - private readonly _host: ReactiveControllerHost; - private readonly _onLoad?: () => void; - private _off: (() => void) | null = null; - - constructor(host: ReactiveControllerHost, onLoad?: () => void) { - this._host = host; - this._onLoad = onLoad; - host.addController(this); - } - - hostConnected(): void { - this._off = eventBus.on('log:loaded', () => { - if (this._onLoad) { - this._onLoad(); - } else { - this._host.requestUpdate(); - } - }); - } - - hostDisconnected(): void { - this._off?.(); - this._off = null; - } -} diff --git a/log-viewer/src/features/analysis/components/LogDiagnosticsView.ts b/log-viewer/src/features/analysis/components/LogDiagnosticsView.ts index c2c27887..9396d682 100644 --- a/log-viewer/src/features/analysis/components/LogDiagnosticsView.ts +++ b/log-viewer/src/features/analysis/components/LogDiagnosticsView.ts @@ -2,11 +2,13 @@ * Copyright (c) 2026 Certinia Inc. All rights reserved. */ import '#vscode-elements/vscode-icon.js'; -import { LitElement, css, html, unsafeCSS } from 'lit'; +import { consume } from '@lit/context'; +import { LitElement, css, html, unsafeCSS, type PropertyValues } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { dispatchInspectorReveal } from '../../../components/inspectorReveal.js'; -import { eventBus } from '../../../core/events/EventBus.js'; +import { logContext } from '../../../core/log/logContext.js'; +import type { LogStore } from '../../../core/log/LogStore.js'; import { formatDuration } from '../../../core/utility/Util.js'; import { formatSOQLToTemplate } from '../../soql/format/formatter.js'; import { SEVERITY_TYPES, type Severity } from '../../soql/services/SOQLLinter.js'; @@ -67,6 +69,11 @@ export class LogDiagnosticsView extends LitElement { @property({ attribute: false }) instances: readonly number[] | null = null; + /** The log on screen, from the app root. */ + @consume({ context: logContext, subscribe: true }) + @property({ attribute: false }) + logStore: LogStore | null = null; + /** The whole log's findings, before any scoping. */ @state() private _all: LogDiagnostics | null = null; @@ -92,18 +99,12 @@ export class LogDiagnosticsView extends LitElement { /** The findings {@link _result} was scoped from. */ private _scoped: LogDiagnostics | null = null; - private _offLogLoaded: (() => void) | null = null; - private _columns = FALLBACK_COLUMNS; private _resize: ResizeObserver | null = null; override connectedCallback() { super.connectedCallback(); - void this._analyse(); - // The inspector paints before the first log is parsed, and it rebuilds only - // on a tab change or a selection. - this._offLogLoaded = eventBus.on('log:loaded', () => void this._analyse()); if (typeof ResizeObserver !== 'undefined') { this._resize = new ResizeObserver(() => this._measure()); this._resize.observe(this); @@ -111,8 +112,6 @@ export class LogDiagnosticsView extends LitElement { } override disconnectedCallback() { - this._offLogLoaded?.(); - this._offLogLoaded = null; this._resize?.disconnect(); this._resize = null; super.disconnectedCallback(); @@ -135,7 +134,10 @@ export class LogDiagnosticsView extends LitElement { this._all && this.instances ? scopeDiagnostics(this._all, this.instances) : this._all; } - override updated() { + override updated(changed: PropertyValues) { + if (changed.has('logStore')) { + void this._analyse(); + } this._measure(); } diff --git a/log-viewer/src/features/analysis/components/__tests__/LogDiagnosticsView.test.ts b/log-viewer/src/features/analysis/components/__tests__/LogDiagnosticsView.test.ts index 586e7729..78ec13ff 100644 --- a/log-viewer/src/features/analysis/components/__tests__/LogDiagnosticsView.test.ts +++ b/log-viewer/src/features/analysis/components/__tests__/LogDiagnosticsView.test.ts @@ -28,9 +28,13 @@ jest.mock('../../services/LogDiagnostics.js', () => ({ }, })); -import { eventBus } from '../../../../core/events/EventBus.js'; +import type { LogStore } from '../../../../core/log/LogStore.js'; import '../LogDiagnosticsView.js'; +const loadLog = (element: HTMLElementTagNameMap['log-diagnostics']) => { + element.logStore = { log: {} } as unknown as LogStore; +}; + const view = async (scope?: { instances: number[] }) => { const element = document.createElement('log-diagnostics'); if (scope) { @@ -286,7 +290,7 @@ describe('log-diagnostics', () => { eventIndex: 1, }, ]; - eventBus.emit('log:loaded', {}); + loadLog(element); await element.updateComplete; await element.updateComplete; expect(text(element, '.title')).toEqual(['Later finding.']); @@ -369,7 +373,7 @@ describe('log-diagnostics', () => { // The next log has nothing at that severity, and with one band left there is // no roll-up to release it with, so the filter must not outlive the list. result = { ...result, diagnostics: [result.diagnostics[1]!] }; - eventBus.emit('log:loaded', {}); + loadLog(element); await element.updateComplete; await element.updateComplete; expect(text(element, '.title')).toEqual(['Noted.']); diff --git a/log-viewer/src/features/app/LogViewer.ts b/log-viewer/src/features/app/LogViewer.ts index 3e04d7db..465e26d9 100644 --- a/log-viewer/src/features/app/LogViewer.ts +++ b/log-viewer/src/features/app/LogViewer.ts @@ -267,9 +267,6 @@ export class LogViewer extends LitElement { // Published before the views render, so every tab reads the same log // whichever one loads first. this._logStore = setCurrentLog(apexLog); - // After the store holds the log, never before: the views still on the event - // read it straight from there. - eventBus.emit('log:loaded', {}); this.logSize = apexLog.size; this.timelineRoot = apexLog; diff --git a/log-viewer/src/features/call-tree/utils/ExecutionHighlights.ts b/log-viewer/src/features/call-tree/utils/ExecutionHighlights.ts index bc88d106..ba7f7fca 100644 --- a/log-viewer/src/features/call-tree/utils/ExecutionHighlights.ts +++ b/log-viewer/src/features/call-tree/utils/ExecutionHighlights.ts @@ -3,7 +3,6 @@ */ import type { ApexLog, LogCategory, LogEvent } from 'apex-log-parser'; -import { currentLogStore } from '../../../core/log/LogStore.js'; import { getEventKey } from './Aggregation.js'; /** One frame on the hot path, entry point first. */ @@ -245,9 +244,3 @@ export function getExecutionHighlights(apexLog: ApexLog): ExecutionHighlights { } return highlights; } - -/** The highlights for the log on screen, or null before the first parse. */ -export function getCurrentExecutionHighlights(): ExecutionHighlights | null { - const apexLog = currentLogStore()?.log; - return apexLog ? getExecutionHighlights(apexLog) : null; -} diff --git a/log-viewer/src/features/database/components/DatabaseTimeTree.ts b/log-viewer/src/features/database/components/DatabaseTimeTree.ts index 1e5a16e6..c9cdface 100644 --- a/log-viewer/src/features/database/components/DatabaseTimeTree.ts +++ b/log-viewer/src/features/database/components/DatabaseTimeTree.ts @@ -1,8 +1,9 @@ /* * Copyright (c) 2026 Certinia Inc. All rights reserved. */ -import { LitElement, css, html, unsafeCSS } from 'lit'; -import { customElement } from 'lit/decorators.js'; +import { consume } from '@lit/context'; +import { LitElement, css, html, unsafeCSS, type PropertyValues } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; import { type CellComponent, type ColumnDefinition, @@ -23,7 +24,8 @@ import { } from '../../../components/locatedRow.js'; import { PANEL_ROW_MENU_ITEMS, runPanelRowAction } from '../../../components/panelRowMenu.js'; import { eventBus } from '../../../core/events/EventBus.js'; -import { LogLoadedController } from '../../../core/events/LogLoadedController.js'; +import { logContext } from '../../../core/log/logContext.js'; +import type { LogStore } from '../../../core/log/LogStore.js'; import { SelectionEchoGuard } from '../../../core/events/SelectionEchoGuard.js'; import { formatDuration, formatInteger } from '../../../core/utility/Util.js'; import { globalStyles } from '../../../styles/global.styles.js'; @@ -43,7 +45,7 @@ import { soqlInlineElement } from '../../soql/format/inlineCell.js'; import { soqlSyntaxStyles } from '../../soql/styles/soql-syntax.css.js'; import { type DatabaseCallNode, - currentDatabaseOverview, + databaseOverview, NO_STATEMENTS, type StatementKind, } from '../services/databaseOverview.js'; @@ -158,9 +160,10 @@ export class DatabaseTime extends LitElement { /** The build in flight; a newer one aborts it, and so does a disconnect. */ private _building: AbortController | null = null; - private readonly _logLoaded = new LogLoadedController(this, () => { - void this._build(); - }); + /** The log on screen, from the app root. */ + @consume({ context: logContext, subscribe: true }) + @property({ attribute: false }) + logStore: LogStore | null = null; static styles = [ globalStyles, @@ -222,7 +225,12 @@ export class DatabaseTime extends LitElement { firstUpdated(): void { this._contextMenu = this.renderRoot.querySelector('context-menu'); - void this._build(); + } + + updated(changed: PropertyValues): void { + if (changed.has('logStore')) { + void this._build(); + } } render() { @@ -271,7 +279,8 @@ export class DatabaseTime extends LitElement { } private async _build(): Promise { - const overview = currentDatabaseOverview(); + const log = this.logStore?.log; + const overview = log && databaseOverview(log); this._building?.abort(); const { signal } = (this._building = new AbortController()); // Wait for the host to lay out before Tabulator measures column widths — @@ -295,8 +304,7 @@ export class DatabaseTime extends LitElement { return; } if (!overview) { - // No log yet, so there is nothing to size a new table against; the load - // event builds it once the figures exist. + // No log yet, so there is nothing to size a new table against. return; } diff --git a/log-viewer/src/features/database/services/databaseOverview.ts b/log-viewer/src/features/database/services/databaseOverview.ts index ce2dcb8d..a763e350 100644 --- a/log-viewer/src/features/database/services/databaseOverview.ts +++ b/log-viewer/src/features/database/services/databaseOverview.ts @@ -10,7 +10,6 @@ import { } from 'apex-log-parser'; import { DEFAULT_NAMESPACE, getCallerNamespace } from '../../../core/utility/CallerNamespace.js'; -import { currentLogStore } from '../../../core/log/LogStore.js'; /** The label for a DML statement whose SObject the log never names. */ export const UNKNOWN_OBJECT = 'Unknown'; @@ -164,12 +163,6 @@ export interface DatabaseOverview { /** Memo per log: the tree never changes after parse, the sections re-render. */ const cache = new WeakMap(); -/** The whole-log figures for the log on screen, or `null` before one resolves. */ -export function currentDatabaseOverview(): DatabaseOverview | null { - const apexLog = currentLogStore()?.log; - return apexLog ? databaseOverview(apexLog) : null; -} - /** {@link DatabaseOverview} for a parsed log, computed once. */ export function databaseOverview(root: ApexLog): DatabaseOverview { const cached = cache.get(root); diff --git a/log-viewer/src/styles/tokens.css b/log-viewer/src/styles/tokens.css index 9626bc27..75009327 100644 --- a/log-viewer/src/styles/tokens.css +++ b/log-viewer/src/styles/tokens.css @@ -63,11 +63,11 @@ --lana-font-ui: var(--vscode-font-family, sans-serif); /* Surfaces — the same registered colors VS Code's own chrome is built from. - `surface-border` is absent before the size-token registry (stable 1.128 ships - none of the `--vscode-surface-*` set), so its fallback chain is load-bearing. */ + `surface-border` ships in no stable VS Code and most themes set no `widget.border`, + so the chain has to reach a colour or every hairline drawn with it disappears. */ --lana-surface-border: var( --vscode-surface-border, - var(--vscode-widget-border, var(--vscode-sideBar-border, transparent)) + var(--vscode-widget-border, var(--vscode-panel-border, rgba(128, 128, 128, 0.35))) ); --lana-header-bg: var(--vscode-tab-activeBackground, var(--lana-editor-bg));