Skip to content

refresh top bar (design tokens + adaptive overflow menu) and tile chrome#151

Merged
korbinian90 merged 10 commits into
niivue:mainfrom
korbinian90:claude/visual-refresh-tokens-ZpqBR
Jun 13, 2026
Merged

refresh top bar (design tokens + adaptive overflow menu) and tile chrome#151
korbinian90 merged 10 commits into
niivue:mainfrom
korbinian90:claude/visual-refresh-tokens-ZpqBR

Conversation

@korbinian90

@korbinian90 korbinian90 commented Apr 20, 2026

Copy link
Copy Markdown
Collaborator

Introduce a token-driven visual layer scoped to the top menu bar and volume tile chrome. Tokens live in a new tokens.css, are exposed to Tailwind via theme.extend (so the rest of the codebase can use bg-bg-3 / text-fg-1 / text-accent / font-ui), and the complex chrome (nv-topbar, nv-pane, nv-pane-label, nv-readout, nv-menu-panel) stays as plain CSS in per-component stylesheets. Status bar, left rail, panels, and command palette are untouched; existing gray-* utilities continue to work.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP

Update (2026-06-13): rebased onto main + adaptive top bar

Rebased onto current main, which had since moved to Tailwind v4, added the Tauri desktop app, and added drag-and-drop image reordering. Conflicts resolved in index.css (v3 @tailwind directives reconciled with v4 @import 'tailwindcss' + @config) and Volume.tsx (kept both main's drag-and-drop machinery and the new tile chrome + x close button).

Adaptive top bar. The menu bar no longer wraps to a second line when horizontal space runs out. A new MenuBar measures the available width with a ResizeObserver and keeps as many top-level menus inline as fit; the rest collapse into a trailing "More" overflow button. A collapsed dropdown expands inline (accordion) inside the popover, which stays robust on the narrow VS Code / Streamlit widths where overflow actually happens. Top-level items are now expressed as a data array so each renders either inline or in the overflow menu; the existing menu primitives (MenuItem / MenuButton / MenuToggle / MenuEntry) are reused unchanged.

Fix: component CSS now ships in production. The refreshed chrome rendered in dev but not in production builds. The package declared "sideEffects": false, so the bare side-effect import './index.css' in the package entry was tree-shaken out of consumers' production bundles, dropping every nv- rule and token. The CSS is now co-located with the components that use it (Menu.tsx, Volume.tsx) and *.css is marked side-effectful, so the chrome ships in dev, app production builds, and the library build.

Verification. type-check, lint, and 71 unit tests pass; PWA and library production builds now contain the nv- styles and tokens; the full PWA e2e suite (50 tests) plus a new MenuOverflow.spec.ts (wide / narrow / after-loading-an-image) all pass on a fresh production build.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings April 20, 2026 00:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a design-token-based styling layer for niivue-react, focusing on the top menu bar and volume tile chrome, and exposes tokens to Tailwind utilities.

Changes:

  • Added tokens.css (CSS variables) and extended Tailwind theme (colors/fonts/radii) to enable bg-bg-*, text-fg-*, text-accent, font-ui, etc.
  • Replaced Tailwind utility styling in top bar/menu and volume tile chrome with nv-* classnames backed by new CSS files.
  • Added @fontsource/inter and @fontsource/jetbrains-mono dependencies and imported font CSS in the VS Code build entry.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pnpm-lock.yaml Locks new @fontsource/* dependencies.
packages/niivue-react/package.json Adds font dependencies (also triggers changeset requirement).
packages/niivue-react/tailwind.config.js Extends Tailwind theme with token-backed colors/fonts/radii.
packages/niivue-react/src/styles/tokens.css Defines global design token CSS variables and density presets.
packages/niivue-react/src/index.css Imports tokens + new chrome CSS and Tailwind layers.
packages/niivue-react/src/main.tsx Imports font CSS assets for the VS Code build entry.
packages/niivue-react/src/components/Menu.tsx Refactors top bar structure and applies new nv-* chrome classes.
packages/niivue-react/src/components/MenuElements.tsx Updates menu element classes to nv-* primitives and menu panel wrapper.
packages/niivue-react/src/components/Menu.module.css Adds CSS definitions for top bar + menu primitives (intended global).
packages/niivue-react/src/components/Volume.tsx Updates tile chrome markup/classes and close button accessibility label.
packages/niivue-react/src/components/Volume.module.css Adds CSS definitions for viewport tile chrome (intended global).
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +1 to +3
/* Top bar + menu primitives — source: prototype styles.css lines 83–143, 605–624.
Imported globally from index.css; classes are plain (not CSS-Module-scoped).
The `nv-` prefix provides the scoping. */
Comment on lines +1 to +3
/* Viewport tile chrome — source: prototype styles.css lines 206–280.
Imported globally from index.css; classes are plain (not CSS-Module-scoped). */

import './index.css'
import '@fontsource/inter/400.css'
import '@fontsource/inter/500.css'
import '@fontsource/inter/600.css'
Comment on lines 64 to 69
"dependencies": {
"@fontsource/inter": "^5.0.0",
"@fontsource/jetbrains-mono": "^5.0.0",
"@niivue/dcm2niix": "^1.2.0",
"@niivue/minc-loader": "^1.0.0"
},
Comment thread packages/niivue-react/src/index.css Outdated
Comment on lines +1 to +3
@import './styles/tokens.css';
@import './components/Menu.module.css';
@import './components/Volume.module.css';
Comment thread packages/niivue-react/src/index.css Outdated
Comment on lines 1 to 7
@import './styles/tokens.css';
@import './components/Menu.module.css';
@import './components/Volume.module.css';

@tailwind base;
@tailwind components;
@tailwind utilities;
@@ -0,0 +1,57 @@
/* Design tokens — source: /tmp/design-project/niivue-vscode/project/styles.css (lines 6–52, 668–679).
claude added 4 commits May 17, 2026 21:07
Introduce a token-driven visual layer scoped to the top menu bar and
volume tile chrome. Tokens live in a new tokens.css (verbatim from the
design prototype), are exposed to Tailwind via theme.extend so the rest
of the codebase can use bg-bg-3 / text-fg-1 / text-accent / font-ui
utilities, and the complex chrome (nv-topbar, nv-pane, nv-pane-label,
nv-readout, nv-menu-panel) stays as plain CSS in per-component
.module.css files — color-mix, backdrop-filter and gradient backgrounds
are kept readable rather than shoved into arbitrary-value utility
strings. Status bar, left rail, panels, and command palette are
untouched; existing gray-* utilities continue to work.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP
- rename Menu.module.css / Volume.module.css to plain .css. They are
  global stylesheets; the .module.css suffix suggested CSS-Modules
  scoping, even though the @import path sidesteps Vite's module
  transform.
- reorder index.css so component CSS is imported with layer(components)
  between @tailwind components and @tailwind utilities. Tailwind's
  preflight base layer now comes before the component rules, so
  element-default resets (e.g. button background/cursor) can't stomp
  .nv-topbtn / .nv-menu-item / etc.
- drop font-weight: 700 from .nv-brand-mark (600 is the heaviest Inter
  weight imported; 600 looks the same at 14px and avoids a synthesized
  bold).
- strip /tmp/design-project paths from header comments; they only made
  sense in the authoring environment.
- add a Changeset entry so @niivue/react gets a patch release note.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP
MCP tools in this sandbox are scoped to korbinian90/niivue-vscode;
upstream reviews live on niivue/niivue-vscode. Record the curl
endpoints for fetching PR/review/comment data so future sessions
don't re-derive them.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP
The volume tile close glyph changed from "X" to "×" (U+00D7) as part of
the visual refresh, breaking `hasText: 'X'` / `:has-text("X")` selectors
in two Playwright specs. Switch both to `getByRole('button', { name:
'Close' })` — semantic and stable against glyph tweaks.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP
@korbinian90 korbinian90 force-pushed the claude/visual-refresh-tokens-ZpqBR branch 2 times, most recently from 26e6f9b to 5b5d746 Compare May 17, 2026 20:33
claude and others added 3 commits May 17, 2026 21:09
probe-mhd-streamlit-shape.spec.ts asserted the legacy single-line
"{location}: {intensity}" readout. The visual refresh splits it into a
structured POS / VAL pair inside .nv-readout, so the concatenated
"32 x 32 x 32: 255" substring never appears. Assert each fragment
separately and require the "VAL" prefix before the intensity value to
keep the match unambiguous.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP
@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

coverage

Overall line coverage: 36.8% (+1.3) vs main

Package Statements Branches Functions Lines
Shared core (packages/niivue-react) 39.2% (+1.6) 42.7% (+1.3) 45.6% (+2.5) 39.5% (+1.7)
apps/pwa 29.5% 33.3% 52.9% 31%
apps/jupyter 14.4% 15.9% 14.9% 14.5%
apps/streamlit 17.8% 5.3% 18.5% 17.7%
apps/vscode 38.7% 39.6% 18.9% 38.1%
apps/desktop-tauri 81.8% 59.1% 78.9% 84.3%

📊 View full report →

@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

🚀 PWA Preview Deployment

Your PWA preview has been deployed!

Preview URL: https://niivue.github.io/niivue-vscode/pr-151/


This preview will be updated automatically when you push new commits to this PR.

github-actions Bot added a commit that referenced this pull request May 17, 2026
@korbinian90 korbinian90 requested a review from Copilot May 17, 2026 23:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment on lines +30 to +32
sm: 'var(--radius-sm)',
DEFAULT: 'var(--radius)',
lg: 'var(--radius-lg)',
Comment on lines +7 to +24
extend: {
colors: {
'bg-0': 'var(--bg-0)',
'bg-1': 'var(--bg-1)',
'bg-2': 'var(--bg-2)',
'bg-3': 'var(--bg-3)',
'bg-4': 'var(--bg-4)',
'bg-5': 'var(--bg-5)',
'fg-0': 'var(--fg-0)',
'fg-1': 'var(--fg-1)',
'fg-2': 'var(--fg-2)',
'fg-3': 'var(--fg-3)',
'fg-4': 'var(--fg-4)',
accent: 'var(--accent)',
line: 'var(--line)',
'line-2': 'var(--line-2)',
'line-3': 'var(--line-3)',
},
Comment on lines +6 to +9
import '@fontsource/inter/400.css'
import '@fontsource/inter/500.css'
import '@fontsource/inter/600.css'
import '@fontsource/jetbrains-mono/400.css'
</div>
</div>
<p className="pl-2">{displayInfo.value}</p>
<p className="pl-2 text-fg-2">{displayInfo.value}</p>
margin-left: 16px;
font-family: var(--font-mono);
}
.nv-menu-div,
@@ -0,0 +1,5 @@
---
"@niivue/react": patch
korbinian90 added a commit that referenced this pull request May 18, 2026
… tests (#175)

* test(coverage): easy wins — jupyter download fix + vscode/shared-core tests

Three coverage easy wins, all surfaced from the published coverage table
on PR #151 (https://niivue.github.io/niivue-vscode/coverage/pr-151/) which
showed jupyter as null and vscode at 7.8%.

1) ci.yml — fix jupyter coverage download path
   Aggregator scans `*/coverage/**/*.json` for `coverage-final.json`.
   apps/jupyter's CI step uploaded coverage as a single-path artifact
   (which strips the parent path) and then downloaded with `path: .`,
   landing files at the repo root with no `coverage/` segment — invisible
   to the aggregator. Same pattern as VS Code's existing fix: download
   under `apps/jupyter/coverage/unit/` to restore the path segment.
   Bumps jupyter from `null` to a real number on the dashboard.

2) apps/vscode tests — three new spec files, 41 new tests
   - test/dispose.test.ts (9 tests) — disposeAll + Disposable LIFO/idempotency
   - test/html.test.ts (6 tests) — CSP locked-down, nonce uniqueness,
     asWebviewUri wiring, no wildcard sources (security guardrail)
   - test/HoverProvider.test.ts (26 tests) — regex link detection across
     all 17 supported extensions, command URI shape, case-insensitivity,
     negative cases
   test/vscode-mock.ts extended with Hover, MarkdownString, Position,
   Range, EventEmitter, Disposable to support the new tests.
   Coverage: HoverProvider 100%, dispose 100%, html 100%.
   Overall apps/vscode: 7.8% → ~34% statements.

3) packages/niivue-react tests — three new spec files, 53 new tests
   - test/utility.test.ts (35 tests) — isImageType extension matrix,
     getMetadataString empty/3D/4D/sub-mm voxel formatting,
     getNumberOfPoints, getNames duplicate-name resolution with
     overlay/layer fallbacks
   - test/keyboardShortcuts.test.ts (12 tests) — matchesShortcut over
     bare keys / ctrl / shift / alt / meta-as-ctrl / case-insensitivity
   - test/readyState.test.ts (6 tests) — ReadyStateManager state machine
     (dom + listener → single send; idempotency; no-window safety)
   Coverage on the tested files: utility 68%, readyState 100%,
   settings 100%, keyboardShortcuts 100%.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(jupyter): cover remaining url-utils exports

Adds tests for the five exports the original url-utils.test.ts didn't
cover:

  getJupyterUrl                — URLExt.join wrapper around PageConfig
  getMhdPairedRawBasename      — parses ElementDataFile field; handles
                                  LOCAL, missing field, quoted values,
                                  subdir-rejection, case insensitivity
  getMhdPairedRawPath          — sibling path computation
  fetchArrayBuffer             — happy path, network failure, HTTP error
                                  with body detail, text() throws,
                                  text/html auth-redirect detection
  fetchJson                    — happy path, HTTP error, generic typing

ServerConnection.makeRequest is mocked via vi.spyOn so we exercise
fetchArrayBuffer/fetchJson without a real HTTP roundtrip. The error
messages tested ("Unexpected HTML response (likely auth redirect)",
HTTP-status-with-truncated-body) are the ones JupyterHub users see in
auth misfires — pinning their shape protects against silent regressions
of user-facing error UX.

Result: url-utils.ts coverage 16% → 100% statements / 86% branches.
Overall apps/jupyter 1.6% → 10.1% (LOC ceiling — viewer.ts and index.ts
together are 77% of source and need JupyterLab test harness to exercise).
+25 tests, 39 total in the file.

* review: tighten test assertions + add changeset

Address all seven Copilot review points on #175.

1) Missing changeset for packages/ + apps/ changes.
   Add .changeset/test-coverage-easy-wins.md with empty frontmatter
   (tests-only — no version bump).

2) HoverProvider getText stub ignored the range, returning the whole line
   regardless. That meant a regression where the provider passed the wrong
   range to getText() would still pass the tests. Fix: slice the line by
   the range's character offsets.

3) Loose command-URI assertion (`/command:niiVue\.openLink\?/` matched
   even though the stubbed Uri.parse mangled the URI). Improve the Uri
   mock to handle opaque-scheme URIs (command:, mailto:, data:) per
   real vscode.Uri behavior, distinguishing hierarchical schemes (file,
   http, https, vscode-remote) from opaque ones via a private _hierarchical
   flag set at parse time. Then assert the parsed scheme, command path,
   and decoded args structure explicitly. Adds an extra test pinning that
   surrounding text never leaks into the args.

4) tryHover swallowed every rejection/exception. Replace with a guard
   that only swallows the implementation's `reject()` (which rejects
   with undefined) and re-throws anything else, so unexpected crashes
   fail loudly.

5) readyState comment had backwards logic ("would still hand us a fresh
   state machine"). Rewrite to describe what resetModules actually does
   — clears the module cache so the next import re-evaluates the module
   and constructs a fresh singleton.

6) isImageType tests only asserted truthiness. Switch to it.each tuples
   that assert the exact returned extension string, so a regression
   that says "yes, supported" but returns the wrong extension still
   fails.

7) CSP wildcard guard regex was too narrow (`(\s|=)\*(\s|;|$)` would
   miss `https://*` or `*.example.com`). Replace with a strict
   `not.toContain('*')` — current CSP contains none; any future addition
   should require explicit reasoning.

* review: fix lint — curly braces + prettier formatting on url-utils.test.ts

ESLint `curly` rule rejected the single-line
`if (opts.textThrows) throw new Error('reader closed')` inside the
mockResponse helper. Adding braces fixes that. Prettier then reflowed
some adjacent lines.

* review: position-aware hover mock, real JupyterHub baseUrl test,
empty-changeset detection, fix it.each title order

Four points raised on the most recent review pass:

1) HoverProvider mock was position-blind — `getWordRangeAtPosition`
   returned the first regex match regardless of the cursor position,
   so a buggy provider that asked about a position outside any link
   could still pass these tests. Mock now respects the position the
   same way the real VS Code API does. `tryHover` now defaults the
   cursor to the middle of the line; the positive tests construct
   their lines so the link straddles the middle. Added a new
   "cursor sits outside the link" negative test that pins down the
   new behavior explicitly (cursor at 0 → no hover, cursor inside
   the URL → hover).

2) getJupyterUrl tests didn't actually exercise the JupyterHub
   prefix-preserving behavior the function exists for. The
   assertions only checked the URL suffix while relying on the
   default PageConfig root; an implementation that ignored the
   configured base URL would have passed them. Replaced with tests
   that explicitly set `PageConfig.setOption('baseUrl', 'http://hub.
   example.com/user/johndoe/')` (with and without trailing slash)
   and assert the full URL — the JupyterHub prefix must appear.
   Restores the original baseUrl in afterEach.

3) Empty-frontmatter changeset would still be counted by
   `.github/workflows/prerelease.yml`, triggering a no-op prerelease
   on merge. Updated the Detect step to skip changesets whose
   frontmatter has no package bumps. Verified locally: with the
   current `.changeset/` it counts `streamlit-perf-followup.md` (1)
   and skips `test-coverage-easy-wins.md` (empty). Future test-only
   PRs benefit from the same treatment.

4) it.each placeholder order on isImageType read as "returns
   scan.nii for .nii" — the input/expected were reversed in the
   title even though the assertion was correct. Changed title to
   "given %s, returns %s" so cases read naturally as "given
   scan.nii, returns .nii".

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
korbinian90 and others added 3 commits June 13, 2026 12:11
Bring the design-token visual refresh up to date with main. Key conflict
reconciliations:

- index.css: main migrated to Tailwind v4 (@import 'tailwindcss' + @config).
  Re-add the token + component-CSS imports on top, keeping Menu.css /
  Volume.css in layer(components) so Tailwind utilities still override them.
- Volume.tsx: main added drag-and-drop reordering (drag handle, drop zones,
  insert bars). Keep all of it and apply the nv-pane chrome + nv-iconbtn
  close button (x glyph, aria-label "Close") from the refresh.
- pnpm-lock.yaml regenerated for the @fontsource/* deps alongside main's
  dependency bumps.

Verified: @niivue/react type-check, build, and 71 unit tests all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n prod

Top bar: replace the flex-wrap second-line behaviour with an adaptive
"priority+" overflow. A new MenuBar measures the available width with a
ResizeObserver and keeps as many top-level menus inline as fit; the rest
collapse into a trailing "More" button. Overflowed dropdowns expand
inline (accordion) inside the popover, which stays robust on the narrow
VS Code / Streamlit widths where overflow actually happens (a side
flyout would clip there). Top-level items are now expressed as a data
array so each renders either inline or in the overflow menu; the
existing menu primitives (MenuItem / MenuButton / MenuToggle / MenuEntry)
are reused unchanged, and a hidden measurer mirrors their widths.

Fix: the refreshed chrome (design tokens, top bar, tile chrome) rendered
in dev but not in production builds. The package declares
"sideEffects": false, so the bare side-effect "import './index.css'" in
the package entry was tree-shaken out of consumers' production bundles,
dropping every nv- rule and token. Co-locate the CSS with the components
that use it (Menu.tsx, Volume.tsx) and mark "*.css" as side-effectful,
so the styles ship reliably in dev, app production builds, and the
library build.

Verified: type-check, lint, 71 unit tests; PWA and library production
builds now contain the nv- styles and tokens; full PWA e2e suite (50
tests) plus a fresh-production Menu e2e run all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three Playwright cases in real Chrome (where ResizeObserver fires on
viewport changes, unlike the headless preview):
- wide viewport keeps all top-level menus inline (no More button)
- narrow viewport collapses overflow into More, and a collapsed dropdown
  expands inline (View -> Axial) inside the popover
- loading the example image reveals ColorScale/Overlay/Header/Navigation,
  which then collapse into More at a constrained width instead of wrapping

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@korbinian90 korbinian90 changed the title refresh top bar and tile chrome with design tokens refresh top bar (design tokens + adaptive overflow menu) and tile chrome Jun 13, 2026
github-actions Bot added a commit that referenced this pull request Jun 13, 2026
@korbinian90 korbinian90 merged commit 6ec393b into niivue:main Jun 13, 2026
13 checks passed
korbinian90 added a commit to korbinian90/niivue-vscode that referenced this pull request Jun 13, 2026
Brings the PWA icon-generator work up to date with main (top bar refresh
niivue#151, vite 8 / vitest 4 upgrades, tauri app, coverage tooling).

Conflict resolution:
- apps/pwa/package.json: kept the PR's @vite-pwa/assets-generator ^1.0.2
  and vite-plugin-pwa ^1.3.0; adopted main's newer vite ^8, vitest ^4,
  @types/* and vite-plugin-virtual ^0.5.0.
- pnpm-lock.yaml: took main's lockfile and reran pnpm install
  --lockfile-only so vite-plugin-pwa/@vite-pwa/assets-generator resolve
  against vite@8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
korbinian90 added a commit to korbinian90/niivue-vscode that referenced this pull request Jun 13, 2026
The niivue#151 top bar shipped a placeholder 'N' glyph as the brand mark.
Swap it for the canonical neon brain logo so the same icon this PR
rolls out to the favicon, PWA, VS Code and JupyterLab also appears
in-app.

- New master: packages/niivue-react/src/assets/niivue-logo.png, a
  96x96 256-color-palette downscale (Lanczos) of apps/pwa/public/logo.png,
  ~3.3 KB. Being under Vite's 4096-byte assetsInlineLimit, it inlines
  as a base64 data URI in every consumer (PWA builds @niivue/react from
  source; VS Code and JupyterLab consume the built lib). Verified: the
  lib build embeds one data:image/png URI and emits no separate file,
  so the webview needs no extra asset request or CSP allowance.
- Menu.tsx: the brand <div>N</div> becomes <img class=nv-brand-mark>.
  alt='' since the adjacent 'niivue' wordmark already names the brand.
- Menu.css: .nv-brand-mark restyled from an accent tile + centered glyph
  to an image tile (object-fit: cover, dark #0a0e13 fallback, kept the
  inset hairline ring; softened the accent glow).

Changeset bumps @niivue/react alongside the existing icon work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
korbinian90 added a commit to korbinian90/niivue-vscode that referenced this pull request Jun 13, 2026
The niivue#151 top bar shipped a placeholder 'N' glyph as the brand mark.
Swap it for the canonical neon brain logo so the same icon this PR
rolls out to the favicon, PWA, VS Code and JupyterLab also appears
in-app.

- New module: packages/niivue-react/src/assets/niivue-logo.ts exports
  the logo as a base64 data URI (a 96x96 256-color-palette downscale of
  apps/pwa/public/logo.png, ~3.3 KB). Embedded as a string rather than
  imported as a *.png on purpose: the PWA and the Streamlit frontend
  type-check @niivue/react *from source* and don't all provide an ambient
  '*.png' module declaration, so a *.png import breaks their type-check.
  A plain string sidesteps that and still inlines (verified: the lib
  build embeds one data:image/png URI and emits no asset file, so the
  VS Code webview needs no extra request or CSP allowance).
- Menu.tsx: the brand <div>N</div> becomes <img class=nv-brand-mark>.
  alt='' since the adjacent 'niivue' wordmark already names the brand.
- Menu.css: .nv-brand-mark restyled from an accent tile + centered glyph
  to an image tile (object-fit: cover, dark #0a0e13 fallback, kept the
  inset hairline ring; softened the accent glow).

Changeset bumps @niivue/react alongside the existing icon work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
korbinian90 added a commit that referenced this pull request Jun 13, 2026
…169)

* fix(pwa): modernize icon generation with @vite-pwa/assets-generator

Replaces the legacy 200x200 grayscale brain master with the canonical
512x512 neon brand icon (downscaled via Lanczos3 from the 1024x1024
upstream at niivue/niivue, sha 97875774f13d), and switches PWA icon
generation to vite-plugin-pwa's integrated pwaAssets option.

Generated PNGs use 256-color palette mode (Sharp's quantize-per-output),
so a full icon set (favicon.ico + pwa-64/192/512 + maskable-512 +
apple-touch-180) totals ~89 KB on disk instead of the inflated PNGs the
old pipeline shipped. Per-purpose icons replace the incorrect
'purpose: "any maskable"' on an unpadded transparent icon: the maskable
variant has the icon's own dark background filling the safe-zone, and
the apple-touch variant is pre-rendered at 180x180 instead of being a
mis-labelled 200x200 PNG.

- new pwa-assets.config.ts with palette PNG output options
- VitePWA gets pwaAssets: { config: true, overrideManifestIcons: true }
  and the manifest icons[] array is removed (auto-injected)
- index.html: favicon now has sizes="any", apple-touch-icon points at
  the generated 180x180 file
- copy-assets.mjs removed: nothing left to copy, all assets generated
- .gitignore: keep apps/pwa/public/logo.png, ignore generated icons
- bump vite-plugin-pwa ^1.1.0 -> ^1.3.0

Replaces #108. Targets main directly (the old PR was on the abandoned
ci_working branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pwa): drop deleted copy-assets.mjs from playwright webServer + align workbox peer deps

Three follow-ups after the icon-generator refresh:

1. apps/pwa/playwright.config.ts webServer.command still invoked
   `node copy-assets.mjs && vite build ...`, which silently killed
   webServer startup on CI now that copy-assets.mjs is gone. Result:
   playwright collected 0 tests and exited 1. Drop the node prefix.

2. apps/pwa/tests/global-teardown.ts tried to rmdir public/, which
   would always fail now that public/logo.png is a tracked file.
   The fs.unlink loop above already removes test fixtures; nothing
   else to clean. Drop the rmdir.

3. vite-plugin-pwa@1.3.0 declares workbox-build/workbox-window peer
   deps as ^7.4.1, but apps/pwa/package.json pinned ^7.3.0 — peer
   warning during install and a real risk of subtle incompat. Bump
   both to ^7.4.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* icons: roll out master logo to vscode + jupyter, drop legacy duplicates

PR #169 modernised the PWA icon pipeline but left the vscode and
jupyter extensions on the legacy 200×200 PNGs (plus stray duplicates
under packages/niivue-react/public/). Extend the cleanup:

- apps/vscode/icon.png (128×128) — marketplace icon, downsized from
  apps/pwa/public/logo.png. Replaces niivue_icon.png.
- apps/vscode/language-icon.png (32×32, transparent) — file-type icon
  for .nii/.dcm/etc., downsized from the existing
  niivue_icon_transparent_contrast.png. Replaces both light/dark
  references in package.json.
- apps/jupyter/style/niivue-icon.png (32×32, transparent) —
  .jp-NiivueFileIcon background-image now points at this co-located
  PNG instead of the static/niivue/* rsync output. Decouples the
  JupyterLab file icon from build:assets.

Resized via sharp (already a transitive dep of @vite-pwa/assets-generator
in PR #169) with the same palette/quality settings as pwa-assets.config.ts:
  node -e 'const s=require("sharp"); const png={quality:80,
  compressionLevel:9,palette:true,colors:256};
  (async()=>{await s("apps/pwa/public/logo.png").resize(128,128).png(png).toFile("apps/vscode/icon.png");
  await s("apps/vscode/niivue_icon_transparent_contrast.png").resize(32,32).png(png).toFile("apps/vscode/language-icon.png");
  await s("apps/vscode/niivue_icon_transparent_contrast.png").resize(32,32).png(png).toFile("apps/jupyter/style/niivue-icon.png");})()'

Deleted:
- apps/vscode/niivue_icon.png
- apps/vscode/niivue_icon_transparent_contrast.png
- apps/jupyter/niivue_icon_transparent_contrast.png (unreferenced)
- packages/niivue-react/public/niivue_icon.png (orphan after #169)
- packages/niivue-react/public/niivue_icon_transparent_contrast.png
- packages/niivue-react/public/favicon.ico (#169 generates a new one
  from apps/pwa/public/logo.png)

Also drop the now-inert "niivue/niivue_icon*.png" rules from
apps/vscode/.vscodeignore — the source files they shielded against
no longer exist in packages/niivue-react/public/.

The PWA build was re-verified end-to-end (manifest icons[] now lists
pwa-64/192/512 + maskable-512; build/ contains apple-touch-180,
favicon.ico generated at 48×48); the vscode extension package builds
cleanly with the new icon references; the jupyter CSS reference was
verified locally (full labextension build needs jupyter CLI not
present in this sandbox — CI will exercise it).

Changeset extended to also bump 'niivue' and '@niivue/jupyter' patch.

https://claude.ai/code/session_01MZgGydjXWry6uru8UGNstP

* feat(react): use the neon brain logo as the top-bar brand mark

The #151 top bar shipped a placeholder 'N' glyph as the brand mark.
Swap it for the canonical neon brain logo so the same icon this PR
rolls out to the favicon, PWA, VS Code and JupyterLab also appears
in-app.

- New module: packages/niivue-react/src/assets/niivue-logo.ts exports
  the logo as a base64 data URI (a 96x96 256-color-palette downscale of
  apps/pwa/public/logo.png, ~3.3 KB). Embedded as a string rather than
  imported as a *.png on purpose: the PWA and the Streamlit frontend
  type-check @niivue/react *from source* and don't all provide an ambient
  '*.png' module declaration, so a *.png import breaks their type-check.
  A plain string sidesteps that and still inlines (verified: the lib
  build embeds one data:image/png URI and emits no asset file, so the
  VS Code webview needs no extra request or CSP allowance).
- Menu.tsx: the brand <div>N</div> becomes <img class=nv-brand-mark>.
  alt='' since the adjacent 'niivue' wordmark already names the brand.
- Menu.css: .nv-brand-mark restyled from an accent tile + centered glyph
  to an image tile (object-fit: cover, dark #0a0e13 fallback, kept the
  inset hairline ring; softened the accent glow).

Changeset bumps @niivue/react alongside the existing icon work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
korbinian90 added a commit that referenced this pull request Jun 13, 2026
Re-applies the .nvd 'Save Scene' action as a BarItem in the new data-driven MenuBar (the old MenuButton render path was rewritten upstream), keeping the viewer-protocol import and the saveScene handler. type-check, lint, build, and 80 unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants