Skip to content
Draft
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: 5 additions & 4 deletions .githooks/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
The `.githooks/` directory contains local git hooks that keep generated source in sync before a commit is created.
The `.githooks/` directory contains local git hooks that run publish-friendly
checks before a commit is created.

The current pre-commit hook runs `npm run sync:linear-layout-examples`. That command rewrites the baked linear-layout examples in the demo extension from the Python `demo_linear_layout.py` source when that source is present. This matters in the LL-viz checkout because the static demo must show the same examples as the Python script without asking contributors to edit generated TypeScript by hand.

Standalone `tensor-viz` checkouts may not include the LL-viz Python demo source. In that case the sync tool leaves the baked examples unchanged, so normal tensor-viz commits are not blocked by a file that only exists in the parent project.
The current pre-commit hook runs `npm run check:ts-docs:staged`. That audits
staged TypeScript files for the documentation and helper-use rules in
`AGENTS.md` without forcing a full codebase audit on every small commit.
3 changes: 0 additions & 3 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@ repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"

npm run check:ts-docs:staged
npm run sync:linear-layout-examples
unset GIT_DIR GIT_WORK_TREE
git -C "$repo_root" add packages/viewer-demo/src/extensions/linear-layout/linear-layout.ts
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules/
python/src/*.egg-info/
python/src/tensor_viz/static/
packages/*/dist/
packages/*/lib/
packages/*/node_modules/
docs/_build/
docs/_extra/
Expand Down
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ The `tensor-viz/` repository is a small monorepo because the project has three j

First, `packages/viewer-core/` is the reusable viewer engine. It knows how to store tensors, parse tensor-view expressions, compute visible coordinates, and render the result. If a behavior should work in any host application, it belongs in core.

Second, `packages/viewer-demo/` is the browser application. It turns the core viewer into a complete UI with tabs, menus, widgets, a command palette, and optional extensions. The demo can add workflows such as linear-layout presets without teaching core about GPU instruction families.
Second, `packages/viewer-demo/` is the browser application and shell package. It turns the core viewer into a complete UI with tabs, menus, widgets, a command palette, and optional extension hooks. The stock tensor-viz app registers no domain-specific extensions; downstream packages such as LL-viz pass extension factories into the shell at startup.

Third, `python/src/tensor_viz/` is the Python transport layer. It converts Python arrays or metadata into the same manifest format that the TypeScript viewer loads, then serves the built frontend and tensor bytes locally.

Expand Down
16 changes: 5 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ npm run check:ts-docs
npm run check:ts-docs:staged
```

The pre-commit hook runs `npm run check:ts-docs:staged` and
`npm run sync:linear-layout-examples`. Enable it with:
The pre-commit hook runs `npm run check:ts-docs:staged`. Enable it with:

```bash
git config core.hooksPath .githooks
Expand All @@ -74,7 +73,7 @@ useful. It requires `OPENAI_API_KEY` unless you pass `--print-prompt`.

| Goal | Command |
| --- | --- |
| Test the linear-layout parser example | `npm run check:ts-docs:llm-example` |
| Test the tensor-view parser example | `npm run check:ts-docs:llm-example` |
| Test the example and auto-apply suggested JSDoc replacements | `npm run check:ts-docs:llm-example -- --apply` |
| Audit staged TypeScript files that have no extra unstaged edits | `npm run check:ts-docs:llm` |
| Audit all staged TypeScript blobs from the git index | `npm run check:ts-docs:llm -- --staged` |
Expand Down Expand Up @@ -102,8 +101,8 @@ reads git-index blobs that may not match the working tree.
Other useful LLM audit options:

```bash
--file=packages/viewer-demo/src/extensions/linear-layout/linear-layout-parser.ts
--symbol=parseLayoutSpecs
--file=packages/viewer-core/src/view.ts
--symbol=parseTensorView
--include-direct-helpers
--limit=10
--batch-size=4
Expand All @@ -115,9 +114,7 @@ Other useful LLM audit options:
- `packages/viewer-core/src/`: reusable viewer engine, layout math, session
model, rendering, and core tests.
- `packages/viewer-demo/src/`: browser demo shell, command palette, widget
lifecycle, extension registry, and app tests.
- `packages/viewer-demo/src/extensions/linear-layout/`: linear-layout extension,
parser/model code, preset catalog, widgets, and tests.
lifecycle, extension registry, extension API exports, and app tests.
- `python/src/tensor_viz/`: Python package, session builder, local server, and
built frontend assets.
- `python/tests/`: Python API and documentation-example tests.
Expand All @@ -131,9 +128,6 @@ Architecture docs live next to the code they describe. Start with:
- [Repository architecture](./ARCHITECTURE.md)
- [Viewer core](./packages/viewer-core/src/ARCHITECTURE.md)
- [Demo app shell](./packages/viewer-demo/src/ARCHITECTURE.md)
- [Linear layout extension](./packages/viewer-demo/src/extensions/linear-layout/ARCHITECTURE.md)
- [Linear layout presets](./packages/viewer-demo/src/extensions/linear-layout/presets/ARCHITECTURE.md)
- [Linear layout widgets](./packages/viewer-demo/src/extensions/linear-layout/widgets/ARCHITECTURE.md)
- [Python package](./python/src/tensor_viz/ARCHITECTURE.md)
- [Maintenance tools](./tools/ARCHITECTURE.md)
- [Browser e2e tests](./packages/viewer-demo/e2e/ARCHITECTURE.md)
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"check:ts-docs:llm": "node tools/check-ts-docs-llm.mjs",
"check:ts-docs:llm-example": "node tools/llm-doc-audit-example.mjs",
"check:ts-docs:staged": "node tools/check-ts-docs.mjs --staged",
"sync:linear-layout-examples": "python tools/sync-linear-layout-examples.py",
"test": "npm run test:unit && npm run test:e2e",
"test:e2e": "playwright test",
"test:unit": "npm run test --workspace @tensor-viz/viewer-core && npm run test --workspace @tensor-viz/viewer-demo && PYTHONPATH=python/src python -m unittest discover -s python/tests -p 'test_*.py'",
Expand Down
2 changes: 1 addition & 1 deletion packages/viewer-core/src/viewer-graphics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
Vector3,
} from 'three';
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import helvetikerBoldFont from 'three/examples/fonts/helvetiker_bold.typeface.json';
import helvetikerBoldFont from 'three/examples/fonts/helvetiker_bold.typeface.json' with { type: 'json' };
import { VIEWER_LIMITS } from './validation.js';

const LABEL_FONT = new FontLoader().parse(helvetikerBoldFont as never);
Expand Down
8 changes: 2 additions & 6 deletions packages/viewer-demo/e2e/viewer-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,10 @@ test('viewer demo boots, paints tensors, and exposes core controls', async ({ pa
// startup
await expect(page.locator('.ribbon')).toBeVisible();
await expect(page.locator('#viewport')).toBeVisible();
expect(await page.locator('.tab-button').count()).toBeGreaterThan(0);

// extension widgets
await expect(page.locator('#linear-layout-preset-widget')).toBeVisible();
await expect(page.locator('#linear-layout-widget')).toBeVisible();
// core widgets
await expect(page.locator('#tensor-view-widget')).toBeVisible();
await expect(page.getByText('Preset', { exact: true })).toBeVisible();
await expect(page.getByText('Layout Specs', { exact: true })).toBeVisible();
await expect(page.locator('#inspector-widget')).toBeVisible();

// viewport paint
await page.waitForFunction(() => (
Expand Down
2 changes: 1 addition & 1 deletion packages/viewer-demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/webp" href="/logo.webp" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Linear Layout Visualizer</title>
<title>Tensor Viz</title>
<script type="module" src="/src/main.ts"></script>
</head>
<body>
Expand Down
21 changes: 18 additions & 3 deletions packages/viewer-demo/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
{
"name": "@tensor-viz/viewer-demo",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "lib/index.js",
"module": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": "./src/index.ts"
".": {
"types": "./lib/index.d.ts",
"import": "./lib/index.js"
},
"./extension-api": {
"types": "./lib/extension-api.d.ts",
"import": "./lib/extension-api.js"
},
"./styles.css": "./lib/styles.css"
},
"files": [
"lib"
],
"scripts": {
"build": "vite build",
"build": "npm run build:lib && vite build",
"build:lib": "tsc -p tsconfig.lib.json && node ../../tools/copy-viewer-demo-lib-assets.mjs",
"dev": "vite",
"prepack": "npm run build:lib",
"test": "vitest run src",
"typecheck": "tsc --noEmit -p tsconfig.json"
},
Expand Down
12 changes: 3 additions & 9 deletions packages/viewer-demo/src/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
The `viewer-demo/src/` directory is the browser app that turns tensor-viz's reusable viewer engine into a mountable demo shell.

The demo now has two layers. `app-entry.ts`, `app-shell.ts`, `app-extension.ts`, `registered-extensions.ts`, and `main.ts` create the page, command palette, sidebar, tab state, viewer instance, and extension lifecycle hooks. Feature-specific behavior lives under `extensions/` and registers widgets, controls, session migration, tensor-view contributions, inspector rows, and render hooks through the extension API.
The demo now has two layers. `app-entry.ts`, `app-shell.ts`, `app-extension.ts`, `registered-extensions.ts`, and `main.ts` create the page, command palette, sidebar, tab state, viewer instance, and extension lifecycle hooks. Feature-specific behavior should live in downstream packages and register widgets, controls, session migration, tensor-view contributions, inspector rows, and render hooks through the extension API.

The stock app currently registers one extension: `extensions/linear-layout/`. That extension owns the linear-layout model, preset catalog, sidebar widgets, hover popup, selection synchronization, and baked fallback tabs. Keep new linear-layout behavior there instead of adding new branches to `app-entry.ts`.
The stock app currently registers no domain-specific extensions. LL-viz imports `startDemoApp(...)` from `@tensor-viz/viewer-demo` and passes its linear-layout extension factory at startup, which keeps GPU instruction families and preset catalogs out of tensor-viz.

`registered-extensions.ts` is the app's extension registry. New demo features should contribute a factory there with widget slots and a `create(...)` function. `app-entry.ts` should stay shell-shaped: it may ask extensions whether a widget is visible, let them capture tab state, collect extra tensor-view sliders, collect inspector coordinate rows, or forward viewer events, but it should not know instruction families, preset fields, or linear-layout tensor metadata. That keeps future extensions from needing to edit the same central file for every new behavior.

The important local guides are:

- `extensions/linear-layout/ARCHITECTURE.md` explains the linear-layout extension boundary.
- `extensions/linear-layout/presets/ARCHITECTURE.md` explains how preset data and selector fields are added.
- `extensions/linear-layout/widgets/ARCHITECTURE.md` explains how sidebar widgets are split and where UI changes belong.

When changing shell behavior, add the smallest app-level test that protects the extension contract. When changing linear-layout parsing or composition, update `extensions/linear-layout/linear-layout.test.ts`.
When changing shell behavior, add the smallest app-level test that protects the extension contract. When changing a downstream workflow, update that workflow's own package and tests instead of adding branches here.
42 changes: 37 additions & 5 deletions packages/viewer-demo/src/app-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,47 @@ import {
labelWithInfo,
selectionEnabled,
} from './app-format.js';
import type { CommandAction, DemoAppExtension, DemoExtensionContext, DemoTensorViewContribution, DemoWidgetSpec } from './app-extension.js';
import type { CommandAction, DemoAppExtension, DemoExtensionContext, DemoExtensionFactory, DemoTensorViewContribution, DemoWidgetSpec } from './app-extension.js';
import { getAppRoot, mountAppShell, renderWebglUnavailable, supportsWebGL, type AppShellWidgetSlot } from './app-shell.js';
import { controlIcons, renderControlDockControls, type ControlSpec } from './control-dock.js';
import { DEMO_EXTENSION_FACTORIES } from './registered-extensions.js';
import './styles.css';

// this file owns the generic demo shell: tabs, widgets, command routing, and
// session loading. feature-specific behavior should enter through DemoAppExtension
// hooks so adding a preset family or widget does not require new shell branches.
const app = getAppRoot();
/**
* Runtime options for mounting the generic tensor-viz demo shell.
*
* @param root - Optional existing application root. When omitted, the shell uses the standard `#app` lookup and creates it if needed.
* @param extensionFactories - Extension factories supplied by the host package. LL-viz passes its linear-layout factory here while standalone tensor-viz starts with an empty extension list.
* @example
* startDemoApp({
* root: document.querySelector<HTMLElement>('#app')!,
* extensionFactories: [linearLayoutExtensionFactory],
* });
*/
export type DemoAppRuntimeOptions = {
root?: HTMLDivElement;
extensionFactories?: readonly DemoExtensionFactory[];
};

/**
* Mount the generic tensor-viz demo shell into the document and wire any host-supplied extensions.
*
* @param options - Optional root element and extension factories supplied by the embedding package.
* @returns Nothing. The call mutates the chosen DOM root by inserting the viewer shell and starts async session/fallback loading.
* @noThrows Startup itself handles WebGL fallback rendering and catches asynchronous session-loading failures by seeding demo tensors; synchronous DOM failures still indicate an invalid host document.
* @example
* startDemoApp();
* // The standalone tensor-viz app starts with no workflow-specific extension widgets.
*
* @example
* startDemoApp({ extensionFactories: [linearLayoutExtensionFactory] });
* // LL-viz receives the same shell plus its linear-layout widgets, controls, and tab hooks.
*/
export function startDemoApp(options: DemoAppRuntimeOptions = {}): void {
const app = options.root ?? getAppRoot();
const extensionFactories = [...(options.extensionFactories ?? [])];

if (!supportsWebGL()) {
renderWebglUnavailable(app);
Expand All @@ -47,7 +78,7 @@ const CORE_WIDGET_SLOTS = [
{ id: 'selection' },
{ id: 'advanced-settings' },
] satisfies AppShellWidgetSlot[];
const EXTENSION_WIDGET_SLOTS = DEMO_EXTENSION_FACTORIES.flatMap((factory) => factory.widgetSlots);
const EXTENSION_WIDGET_SLOTS = extensionFactories.flatMap((factory) => factory.widgetSlots);
const {
viewport,
tabStrip,
Expand Down Expand Up @@ -238,7 +269,7 @@ const coreWidgetSpecs: DemoWidgetSpec[] = [
},
];

const extensions: DemoAppExtension[] = DEMO_EXTENSION_FACTORIES.map((factory) => factory.create(extensionContext));
const extensions: DemoAppExtension[] = extensionFactories.map((factory) => factory.create(extensionContext));
const widgetSpecs = [...extensions.flatMap((extension) => extension.widgets), ...coreWidgetSpecs];
const widgetSpecById = new Map(widgetSpecs.map((spec) => [spec.id, spec]));
// widgets are looked up once from shell slots, then driven by DemoWidgetSpec.
Expand Down Expand Up @@ -3585,3 +3616,4 @@ tryLoadSession().then(async (loaded) => {
seedDemoTensor();
});
}
}
18 changes: 18 additions & 0 deletions packages/viewer-demo/src/extension-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// side-effect-light package surface for external demo extensions.
// unlike index.ts, this module never imports app-entry, so unit tests and model
// code can use extension contracts without booting the viewer or renderer.
export { escapeHtml, escapeInfo, infoButton, labelWithInfo } from './app-format.js';
export { controlIcons, renderControlDockControls } from './control-dock.js';
export type { AppShellWidgetSlot } from './app-shell.js';
export type { ControlSpec } from './control-dock.js';
export type {
CommandAction,
DemoAppExtension,
DemoExtensionContext,
DemoExtensionFactory,
DemoInspectorCoordEntry,
DemoTensorViewContribution,
DemoTensorViewSliderSpec,
DemoWidgetSpec,
LoadedSessionTab,
} from './app-extension.js';
14 changes: 0 additions & 14 deletions packages/viewer-demo/src/extensions/linear-layout/ARCHITECTURE.md

This file was deleted.

Loading
Loading