Skip to content

Stage 0: Foundation Audit

Target versions: v1.13.0 → v1.15.0
Prerequisite: None (start here)
Next stage: 01-render-engine-performance.md


Goal

Establish an honest baseline. Fix stubs falsely marked as complete, add CI quality gates, align package exports with the build, and raise test coverage in core areas so future stages do not regress.

This stage also closes critical visual fidelity gaps: full multi-pane stack export (PNG/SVG at exact layout positions), horizontal pane layout alongside the existing vertical stack, and axis text/line rendering sharpness on par with HTML.

This stage delivers trust — users and contributors can rely on what the docs and API claim.


Current state

What works well (v1.13.0)

AreaEvidence
Dual-canvas render loop (WebGL + 2D overlay)src/core/chart/ChartRenderLoop.ts
Stacked multi-pane charts (1–5 panes, vertical + horizontal)src/core/stacked/createStackedChart.ts
X/Y-axis sync, cursor sync, selection sync, pan/zoom propagationsrc/core/sync/index.ts
Stack + per-chart export (PNG/JPEG/WebP/SVG)stackExport.ts, PluginSnapshot, SVGExporter.ts
19 technical indicator calculation functionssrc/plugins/analysis/indicators.ts
Composite indicator pane buildersrc/core/indicator/buildIndicatorPane.ts
Candlestick renderingsrc/renderer/CandlestickRenderer.ts
CI workflow (test + build).github/workflows/ci.yml
65+ passing unit testspnpm test

What is broken or misleading

ItemFileProblem
PluginForecastingsrc/plugins/forecasting/algorithms.tsUnknown methods still throw (by design)
Custom patternssrc/plugins/pattern-recognition/patterns.ts:631Custom pattern registration returns errors
WebGPU renderersrc/core/chart/ChartCore.ts:317Warns "experimental and not yet implemented"
ESLint in CINo ESLint config in repo yet (0.10 deferred)
Visual regression testsExport/layout screenshot tests not automated (0.27, 0.44 deferred)

Test coverage snapshot

  • 18 .test.ts files covering sync, stacked, indicators, scaling, formatting, SVG export, tooltips, forecasting
  • Vitest coverage ≥45% lines on Stage 0 core scope (pnpm test:coverage); DOM tests use happy-dom
  • Tests run in node / happy-dom environments (stack export integration still manual/browser)

Work items

P0 — Stub audit and fixes

IDTaskPriorityComplexityDefinition of done
0.1Implement ChartGroup.handleSelectionP0MediumSelecting a point/region on master propagates to slaves when syncSelection: true; covered by unit tests in src/core/sync/index.test.ts
0.2Implement or remove PluginSyncP0LowEither wire to ChartGroup with real groupId behavior, or mark @deprecated and document ChartGroup as the canonical API
0.3Audit all plugins marked "complete" in legacy roadmapP0MediumSpreadsheet or markdown table: each plugin → complete / partial / stub; update docs to match
0.4Fix PluginForecasting or narrow documented APIP1MediumEither implement ARIMA/simple methods natively, or remove throwing methods from public exports and docs
0.5Implement or remove custom pattern APIP1HighCustom pattern registration works end-to-end, or API is removed with migration note

P0 — Package and build integrity

IDTaskPriorityComplexityDefinition of done
0.6Add ./react export to package.jsonP0Lowimport { VeloPlot } from 'velo-plot/react' resolves; types included
0.7Align vite.config.lib.ts entries with all package.json exportsP0MediumEvery declared subpath builds; CI verifies with pnpm build
0.8Document bundle strategy (core vs full vs future trading)P1LowSection in README and installation guide

P0 — CI and quality gates

IDTaskPriorityComplexityDefinition of done
0.9Add .github/workflows/ci.ymlP0LowRuns on PR + push to main: pnpm install, pnpm test, pnpm build
0.10Add lint to CI (ESLint if configured, or add config)P1MediumCI fails on lint errors
0.11Enable Vitest coverage with baseline thresholdP1Lowcoverage reporter enabled; threshold ≥15% lines (raise each stage)
0.12Pin GitHub Actions to Node 24 runtime (v5/v6 actions)P1LowNo Node 20 deprecation warnings in CI logs

P1 — Core test expansion

IDTaskPriorityComplexityDefinition of done
0.13Tests for buildIndicatorPane edge casesP1MediumNew file src/core/indicator/buildIndicatorPane.test.ts
0.14Tests for createStackedChart resize + sync optionsP1MediumExtend createStackedChart.test.ts
0.15Tests for NavigationUtils volume pinning regressionP1LowAlready exists — ensure CI runs them
0.16Integration test: stacked + indicator + sync smokeP1MediumOne test creates 3-pane stack, syncs pan, asserts bounds

P2 — Documentation hygiene

IDTaskPriorityComplexityDefinition of done
0.17Archive legacy roadmap (done)P0Lowdocs/ROADMAP-LEGACY.md with redirect notice
0.18Add "Known limitations" section to chart-sync and stacked-chart API docsP1LowDocuments syncSelection status until fixed
0.19Strict CHANGELOG policy in CONTRIBUTING.mdP2LowEvery PR with user-facing change updates CHANGELOG

P0 — Multi-chart export audit (SVG + full-stack snapshot)

IDTaskPriorityComplexityDefinition of done
0.20Audit exportToSVG() and unify with PluginSnapshotP0MediumDocument current SVG capabilities in SVGExporter.ts; decide merge path vs separate stack.exportSVG()
0.21Expose real SVG export (format: 'svg')P0HighVector SVG with series paths + tick labels (not raster embedded in SVG wrapper); wired to chart.snapshot.takeSnapshot({ format: 'svg' })
0.22Implement stack.snapshot() / stack.exportImage()P0HighComposes all pane canvases (WebGL + overlay) into one image at exact layout positions
0.23Position capture via layout rectsP0MediumUse getBoundingClientRect() of each pane wrapper relative to stack container to place pane composites
0.24Respect current view state (zoom/pan/crosshair)P0MediumExport reflects visible bounds and overlay state at capture time — WYSIWYG of what user sees
0.25Legend and divider inclusion policyP1MediumDocument and implement: legend (DOM) rasterized or omitted; dividers included in stack composite
0.26High-DPI stack exportP1Mediumresolution: '4k' | '8k' scales full stack via DPR bump on all panes before composite
0.27Visual regression test: 3-pane stack exportP1MediumExported PNG matches DOM layout within 1px tolerance at 1x DPR

Current export pipeline (v1.13):

Per chart:  webglCanvas + overlayCanvas → compositionCanvas → toDataURL (PNG/JPEG/WebP)
            OR exportToSVG() → vector SVG with tick labels
Per stack:  stack.exportImage() iterates panes + composites by layout rect

Key files: src/plugins/snapshot/index.ts (L88–111), src/core/chart/ChartExporter.ts, src/core/chart/exporter/SVGExporter.ts, src/core/stacked/createStackedChart.ts.

P1 — Multi-Pane Stack: horizontal layout

IDTaskPriorityComplexityDefinition of done
0.28Add direction?: 'vertical' | 'horizontal' to StackedChartOptionsP1LowDefault 'vertical' — no breaking change
0.29Horizontal flex layout in createStackedChart.tsP1HighflexDirection: 'row' when horizontal; pane sizing uses width ratios instead of height
0.30Vertical dividers in paneResize.tsP1Highcursor: ew-resize, drag on deltaX, applyRatios distributes width
0.31Aligned top/bottom margins (horizontal mode)P1MediumcomputeAlignedTopMargin / computeAlignedBottomMargin mirror current left/right logic
0.32sharedYAxis?: 'left' | 'none' optionP1MediumAnalogous to sharedXAxis: 'bottom' — Y labels/ticks only on first pane when 'left'
0.33Per-pane X axis when horizontalP1MediumEach pane shows its own X axis at bottom (no shared X across horizontal panes)
0.34Adapt paneAxis.ts for width-based tick countP1LowtickCountForPaneWidth() when shared axis is vertical
0.35Sync defaults for horizontal layoutP1MediumDefault sync axis flips to 'y' when direction: 'horizontal'; document in API
0.36Stack export works in horizontal layoutP1Mediumstack.snapshot() composites side-by-side panes correctly
0.37Update stacked-chart API docs and pane-stack exampleP1LowDocument horizontal mode; add demo preset in PaneStackDemo

Layout model (horizontal):

mermaid
flowchart LR
  subgraph stackContainer [StackContainer flexDirection row]
    Pane1[Pane1 chart]
    Div1[Divider ew-resize]
    Pane2[Pane2 chart]
    Div2[Divider ew-resize]
    Pane3[Pane3 chart]
  end
  SharedY[sharedYAxis left on Pane1]
  Pane1 --> SharedY

Key files: src/core/stacked/createStackedChart.ts (L191–192, L42–57), src/core/stacked/paneResize.ts, src/core/stacked/paneAxis.ts, src/core/stacked/types.ts.

P1 — Axis text and number rendering fidelity audit

IDTaskPriorityComplexityDefinition of done
0.38Audit axis text vs HTML reference overlayP1MediumDebug page overlays HTML/CSS tick labels on canvas labels; document offset/blur delta in pixels
0.39Pixel-snap 1px grid and axis linesP1MediumLines at Math.floor(coord) + 0.5 in CSS space before DPR transform — crisp 1px lines
0.40Evaluate tick label coordinate snappingP1MediumSnap fillText positions to integer CSS coords where alignment allows; no visible centering regression
0.41Reconcile WebGL and overlay backing-store sizesP0LowNativeWebGLRenderer.resize() uses same Math.round(rect * dpr) as resizeCanvases
0.42Font rendering consistency auditP1LowVerify labelSize, fontFamily, textBaseline, textAlign in OverlayRenderer.ts match theme docs
0.43Stacked pane compact margins vs label clippingP1MediumSmall panes (after resize) do not clip or misalign tick numbers — test with 3-pane stack at min height
0.44Before/after visual regression capturesP1MediumScreenshot comparison at 1x and 2x DPR for single chart and 3-pane stack

Known causes of "shifted" or blurry text (audit findings):

CauseLocationFix
Sub-pixel tick positions from scale.transform()OverlayRenderer.ts L325+Snap lines; evaluate label snap
No pixel snapping on 1px grid linesOverlayRenderer.ts drawGridfloor(x) + 0.5 convention
WebGL canvas size not roundedNativeWebGLRenderer.ts L90Align with ChartSetup.ts L203
Canvas vs HTML subpixel renderinginherent Canvas 2D limitationDocument; snap mitigates blur
Legend DOM vs canvas text (different render paths)ChartLegend.ts vs overlayUnify export path in 0.25

Key files: src/core/OverlayRenderer.ts (L292–437), src/core/chart/ChartSetup.ts (L203–223), src/renderer/native/NativeWebGLRenderer.ts (L88–97).


Risks

RiskMitigation
Removing stub APIs breaks early adoptersDeprecate in v1.14, remove in v2.0 with migration guide
Coverage threshold blocks mergesStart low (15%), increase per stage
Build entry alignment reveals dead exportsRemove unused exports or implement missing builds
Stack export composite misaligns panes after resizeUse layout rects at capture time; test with dragged dividers
Horizontal layout breaks existing vertical stacksDefault direction: 'vertical'; no API change for existing users
Pixel-snapping shifts tick label alignmentTest center/right/left alignments; only snap where visually neutral
SVG export scope creep (full vector parity)Ship PNG stack export first (0.22); SVG with tick labels as stretch (0.21)
Visual regression tests flaky across platformsTolerance threshold (1px); run in CI with fixed DPR=1

Exit checklist (v1.15.0)

  • [x] syncSelection works and is tested
  • [x] Plugin audit table published in docs/PLUGIN-STATUS.md
  • [x] ./react export works
  • [x] All package.json exports build successfully
  • [x] CI workflow green on every PR (test + build)
  • [x] Vitest coverage ≥45% lines (pnpm test:coverage on Stage 0 core scope)
  • [x] No public API method throws not implemented without @experimental tag (all typed ForecastingMethod values implemented)
  • [x] CHANGELOG entries for v1.13.0
  • [x] Full-stack snapshot/export works (PNG minimum) with panes at exact layout positions
  • [x] Multi-Pane Stack supports horizontal layout (direction: 'horizontal') with resizable dividers and aligned shared axis
  • [x] Axis/grid text and lines pixel-snapped; WebGL and overlay canvas backing-store sizes reconciled

Suggested release cadence

VersionFocus
v1.13.0CI workflow, ./react export, syncSelection fix, WebGL/overlay size reconciliation (0.41)
v1.14.0Plugin audit, forecasting/pattern API cleanup, axis text fidelity audit (0.38–0.40)
v1.15.0Build alignment, coverage threshold, stack export PNG (0.22–0.24), horizontal layout MVP (0.28–0.35), SVG export (0.21)

Released under the MIT License.