10 Commits
Author SHA1 Message Date
Jannis Braun cfe4fd80c4 fix(icons): render small favicons from 3D raster to kill white tab-border
The flat app-icon.svg's gradient B mark has a bright (#fff) sheen that runs
to the badge perimeter with no dark separation. At favicon sizes (16/32px)
that edge anti-aliases into a white halo that reads as a border around the
icon — visible in Safari browser tabs, and the same defect in the small
Windows .ico / Linux launcher reps that also rendered from the flat SVG.

The committed 3D raster masters (used by every >=128px output already) frame
the mark in a dark surround and stay clean down to 16px. Set RASTER_THRESHOLD
0 so all app-icon sizes route through the raster path; the flat SVG is kept
as a gated source, re-enablable only with a corrected flat mark. Regenerated
favicons + small desktop reps; output remains byte-deterministic. Updated the
generator header/comments, README source matrix, and the dated icon spec.
2026-07-01 00:58:51 +02:00
Jannis Braun 52b5f8e2b1 chore(brand): refresh app icons + add brand source artwork
Replaces desktop and web icon binaries with the new brand mark, ships
PNG raster exports under assets/brand/ alongside the existing SVG
sources, adds Alternative Styles artwork variants, and updates the
icon generation script and System Prompt doc. deploy.sh excludes
the new local-only artifacts (*.rtfd, assets/brand, electronbuild.sh,
multi-platform-roadmap.md) so they don't get rsync'd to the live
boxes. Drops ARCHITECTURE_AUDIT.md (obsolete).
2026-05-04 00:48:11 +02:00
Jannis Braun 2b0f93ec62 polish(desktop): rename userData folder to Backspace with first-launch migration
Electron derived userData from package.json's `@backspace/desktop` name, leaking
the monorepo's pnpm scope into ~/Library/Application Support/. Now `app.setName`
runs at module load before any userData consumer, and a one-shot migration
atomically moves the historical folder to <appData>/Backspace, cleaning the
empty @backspace/ parent. Conservative on conflict — never clobbers an existing
populated target. EXDEV fallback to recursive copy. Smoke-recovery path flipped
back to Backspace.
2026-05-03 14:36:34 +02:00
Jannis Braun 309484dca9 fix(testing): smoke-recovery.sh — correct userData path, http.server scenarios, missing helper
Three issues found while running the script for the first time:

1. userData path was hardcoded to ~/Library/Application Support/Backspace.
   Electron actually uses ~/Library/Application Support/@backspace/desktop
   (derived from package.json#name when app.name is not set explicitly).
   Scenario 2's bad URL was being written to the wrong location, so the app
   never loaded it and recovery never entered. Path corrected.

2. Scenario 4's VITE_FORCE_BOOT_STALL approach didn't actually stall anything
   in practice: the desktop loads the user's REMOTE instance URL, which
   serves its own web bundle from the server. The locally rebuilt stalled
   bundle is never used at runtime. Replaced with a tiny static HTML page
   served via `python3 -m http.server` and pointed at via BACKSPACE_URL.
   The page has no window.backspace integration, so it never pings,
   guaranteeing the boot timer fires. No web/desktop rebuilds needed.

3. Scenario 13 (positive control: rendererReady ping disarms the timer)
   used assert_no_log which was never defined. The function is now defined
   alongside wait_for_log. Scenario 13 now correctly verifies that a page
   that DOES call rendererReady never triggers recovery.

All three automated scenarios now pass:
  ✓ Scenario 2:  load-failed recovery entered
  ✓ Scenario 4:  renderer-stalled recovery entered
  ✓ Scenario 13: boot timer correctly disarmed by rendererReady ping
2026-05-03 14:02:05 +02:00
Jannis Braun 7e30db3773 feat(testing): smoke-test script + VITE_FORCE_BOOT_STALL gate
- App.tsx and main.tsx ErrorBoundary gate rendererReady() on
  VITE_FORCE_BOOT_STALL build env var. When set, the ping is suppressed
  so the main-process boot timer fires (exercising the renderer-stalled
  recovery path without hand-editing source).
- vite-env.d.ts declares the env var type so TS doesn't complain.
- scripts/smoke-recovery.sh automates scenarios 2 (bad URL → load-failed)
  and 4 (forced stall → renderer-stalled), grepping stderr for the
  [recovery] entered: ... lines added by the prior commit. Backs up and
  restores the user's instance-url.json. Manual scenarios documented in
  the script header.
2026-05-03 13:21:54 +02:00
Jannis Braun de2ef10129 fix(brand): home tile = Element 1 with #000 bg, drop tile lavender
Iterating-by-paint-over wasn't working. Going back to first
principles: the brand IS the badge — full Element 1 mass, mark with
its own internal padding, dark frame around it. That's what reads as
"Backspace" on every other surface (dock, taskbar, favicon, apple-
touch). The sidebar home tile should look the same.

The earlier "ugly grey" complaint about Element 1 in the sidebar
came down to one specific colour mismatch: the source SVG's
#1d1d1b (warm near-black) sat next to the sidebar's #1a1a23 (cool
near-black) and read as an off-grey rectangle. Fix: in the generator,
do a string-replace `#1d1d1b → #000000` on app-icon.svg before
rendering logo.png only — every other output (.icns, .ico, favicons,
PWA) keeps the original brand `#1d1d1b`. Pure black against the
sidebar's #1a1a23 reads as a deliberately darker tile, not a hue
mismatch.

Reverts the SpaceSidebar lavender-tile workaround: with the badge
filling the tile via object-cover, the button's overflow-hidden +
rounded-[20px → 13px] morph already animates the badge cleanly.
backgroundStyle for type === 'dm' returns undefined again; deps trim
back to what they were.

Determinism gate verified — only logo.png changed (other 21 outputs
byte-identical across two regen runs).
2026-04-27 15:39:54 +02:00
Jannis Braun 7a8d0ce0e2 fix(brand): logo.png is bare mark on transparent, 75% padded
The previous Element-1 (full badge) approach produced a visible
warm-grey #1d1d1b square inside the cool-dark #1a1a23 sidebar — the
two near-blacks differ enough in hue to read as a foreign rectangle
in the slot.

The right answer (third time lucky): the SpaceSidebar's 40×40
rounded-[20px] tile already IS the dark squircle frame. logo.png now
composites the bare gradient mark at 75% on a transparent canvas:
the sidebar's tile bg shows through, the 25% padding keeps the mark
off the squircle edges and clear of the active-state ring, and there
is no foreign-bg colour conflict.

Refactored writeMaskablePng → writeCenteredMarkPng to share one code
path between the maskable PWA (opaque #1d1d1b background, 60% mark)
and the in-app logo (transparent background, 75% mark). Both use the
same composition pipeline; only canvas / scale / bg differ.

Only logo.png regenerated; other 21 outputs byte-identical
(determinism gate verified twice).
2026-04-27 15:20:29 +02:00
Jannis Braun 6b71aecc28 fix(brand): logo.png uses full badge, not bare mark
The SpaceSidebar's home tile is a 40×40 rounded-[20px] squircle with
object-cover. The original spec assumed the sidebar provides "the
colour-circle frame," so logo.png shipped as the bare gradient B on
transparent. In practice the tile is a transparent slot — the bare
mark touched the squircle edges (no internal padding), conflicted
with the active-state ring, and read as poorly-integrated.

Switching to the full badge (Element 1 / app-icon.svg): the badge's
own dark squircle bg fills the tile, its internal padding around the
mark keeps the gradient off the edges, and the result reads as a
clean Discord-style "home space" tile alongside the user's other
spaces.

Only logo.png regenerated; remaining 21 outputs byte-identical
(determinism gate verified). Spec output matrix and generator README
updated to reflect the corrected source mapping.
2026-04-27 15:06:22 +02:00
Jannis Braun 81ad0292a5 fix(brand): close Task 3 spec gaps surfaced by code review
Three issues caught by the code review of scripts/gen-icons.mjs:

(1) Missing source SVG validation. Spec requires the script "refuses
to run if any source SVG is missing." Added an existsSync guard at the
top of main() that throws a targeted message rather than letting sharp
fail with a raw ENOENT.

(2) Summary table missing byte sizes. Spec says the summary prints
"each output, its size in pixels, its file size in bytes." trace() now
captures statSync().size; the printout has a size column with
human-readable formatting and a total at the end.

(3) writeMaskablePng's intermediate buffer used .png() without the
compressionLevel/palette options that every other render uses. Zero
functional impact (the buffer is only piped into composite, never
written to disk) but inconsistent and misleading. Aligned with the
rest of the script.

README: replaced 'git add -A' with explicit directory paths matching
the project's convention — the generator writes to a fixed set of
three directories and shouldn't accidentally stage unrelated
working-tree changes during a regen.

Plan updated to match the corrected code so Task 3's source-of-truth
stays consistent.
2026-04-27 14:29:05 +02:00
Jannis Braun bdfd38bc90 feat(brand): add SVG-driven icon generator (scripts/gen-icons.mjs)
Single Node ESM script. Reads assets/brand/*.svg, writes the full
desktop + web icon set deterministically. Idempotent; safe to re-run.
Companion README documents the version-bump determinism caveat.
2026-04-27 14:20:34 +02:00