Skip to content

fix(icon-picker): Upload tab is a drop zone, not a label crammed into a 28px icon button - #513

Open
th3-br41n wants to merge 3 commits into
mainfrom
fix/icon-picker-upload-tab
Open

th3-br41n wants to merge 3 commits into
mainfrom
fix/icon-picker-upload-tab

Conversation

@th3-br41n

@th3-br41n th3-br41n commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What the owner saw

Notes → icon picker → Upload tab: "Choose image…" wrapped onto two lines with a 28×28 outlined square sitting on top of the text, a vast empty body around it, and a bare bordered footer band with nothing in it. Filed as F-524 in the harness friction log.

Root cause

UploadTab reused .icon-picker__action — the 28×28 icon-only class built for the trash button — for its text button, so the label overflowed a fixed square. The footer <div> rendered on every tab though only Emoji / Icon have a row for it. Same two tabs, three more defects: the Library grid lacked flex: 1; min-height: 0 (a long library clips instead of scrolling), its loading state said "Uploading…", and the placeholder copy leaked dev jargon ("land with the entities write half", "SDK's icons service"). The cover picker already had the right affordance one folder over — the two pickers had drifted.

Fix

  • Upload is one drop zone filling the body: click to browse or drag an image onto it; extension-checked on both paths (a drop bypasses the input's accept); inline role="alert" error. Built on a shared .icon-picker__dropzone that the cover picker now rides too (its base rules moved out of cover-picker.css; only the compact "replace image" modifier stays cover-local).
  • Footer renders only on Emoji / Icon.
  • Library owns the scroll, says "Loading…", and names each cell.
  • IconPickerLabels gains dropHint / unsupportedFile / loading / libraryItem; Notes wires them in six locales; *Pending defaults are plain language.

Verification

  • packages/sdk + apps/notes vitest: 347 files / 3630 tests green; 5 new tests (drop-zone render + no footer, drop → upload → pick, non-image rejected, library loading → cells, no-service placeholder).
  • bun run lint:apps all gates green (css-tokens, accent-fill, control-faces, i18n parity …); biome clean; packages + notes + contacts tsc clean.
  • Headless render of the SDK component in mint and default-dark: Upload rest / hover / focus, Library empty / 30 items, no-service placeholder, cover Image tab plain + compact.

Second commit — the same bug class, swept across the fleet

  • Upload + Library were dead placeholders in six more apps (Calendar, Database, Files, Journal, Tasks, Whiteboard): the picker-host bridge they open the picker through had no way to pass services.icons, and only Notes + Contacts had hand-rolled an adapter each. One shared iconUploadFromIcons now lives in the SDK; openIconPicker / createIconPickerButton / <IconPickerButton> take the raw service; every host's runtime type declares icons; Notes + Contacts drop their copies.
  • Eleven user-facing strings leaked engineering vocabulary, rewritten in plain language in six locales: Database's seven "needs the entities service (not exposed by this shell)" statuses, Graph's "needs the shared entities service", Preview's "Renderer for {kind} not yet wired", the file-cell "uploads land when the storage upload API is wired up" (Notes catalog + SDK default), and the Settings embed-providers hint that still promised "the Stage 9 embed sandbox".
  • Swept for other text labels on fixed icon-sized classes: none (Bookmarks' group-by button overrides the square explicitly).
  • Tests: picker-host threads icons into a live Upload tab / keeps the placeholder without it; adapter unit tests; two pinned strings updated. Typecheck for packages + all ten touched apps, biome, lint:apps, and the SDK + nine app suites (725 files) green.

Security

No new IPC or capability surface. The drop path feeds the same iconUpload.upload(filename, bytes) as the file input did; it now also rejects non-image extensions before the bytes leave the renderer.

Follow-up (not in this PR)

packages/shell/src/renderer/ui/icon-picker.tsx is a second, older copy of the same four-tab picker with its own CSS and drop zone — the privileged renderer should ride the SDK component so this drift class cannot recur.

🤖 Generated with Claude Code

th3-br41n and others added 3 commits October 20, 2026 17:27
…y emoji as a broken image

`build.extraResources` copied art/wallpaper and the tray/app icons but
never art/emoji, so the packaged app (0.15.0) had no WebP set for the
brainstorm://emoji handler to serve: every glyph in the Emoji tab and the
category strip 404'd and showed as a broken-image placeholder. Dev reads
packages/shell/art straight from source, which is why it never showed there.

- add the art/emoji extraResources entry (*.webp)
- preview-package.mjs now fails when art/emoji is missing from the bundle
- packaging-resources.test.ts pins the extraResources list to what the
  protocol handler reads from process.resourcesPath/art

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… a 28px icon button

The SDK picker's Upload tab reused `.icon-picker__action` — the 28×28
icon-only class built for the trash button — for its "Choose image…"
text button, so the label wrapped and overflowed a fixed square. The
footer band rendered on every tab though only Emoji / Icon have a row
for it, the Library grid could not scroll, its loading state said
"Uploading…", and the placeholder copy leaked dev jargon.

- Upload: one drop zone filling the body (click to browse or drag an
  image; extension-checked on both paths, inline `role="alert"` error),
  built on a shared `.icon-picker__dropzone` that the cover picker now
  rides too (its base rules moved out of cover-picker.css).
- Footer renders only on Emoji / Icon.
- Library: `flex: 1; min-height: 0` so it owns the scroll, a "Loading…"
  label, an accessible name per cell.
- Labels `dropHint` / `unsupportedFile` / `loading` / `libraryItem` on
  `IconPickerLabels`; Notes wires them in six locales; the `*Pending`
  copy is plain language.

Tests: drop-zone render + no footer, drop → upload → pick, non-image
rejected, library loading → cells, no-service placeholder.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…nguage copy for eleven dev-jargon strings

Sweep after the Upload-tab fix, for the same bug class across the fleet.

- The Upload + Library tabs were permanent "not available" placeholders
  in Calendar, Database, Files, Journal, Tasks and Whiteboard: the
  picker-host bridge those apps open the picker through had no way to
  pass the icons service, and only Notes + Contacts had hand-rolled an
  adapter. One shared `iconUploadFromIcons` now lives in the SDK;
  `openIconPicker` / `createIconPickerButton` / `<IconPickerButton>`
  take the runtime's `services.icons` and adapt it; every host's runtime
  type declares `icons`; Notes + Contacts drop their copies.
- User-facing strings that leaked engineering vocabulary are rewritten
  in plain language across six locales: Database's seven "needs the
  entities service (not exposed by this shell)" statuses, Graph's
  "needs the shared entities service", Preview's "Renderer for {kind}
  not yet wired", the file-cell "uploads land when the storage upload
  API is wired up" (Notes catalog + SDK default), and the Settings
  embed-providers hint that still promised "the Stage 9 embed sandbox".

Tests: picker-host threads `icons` into a live Upload tab / keeps the
placeholder without it; adapter unit tests; the two pinned strings
updated.

Co-Authored-By: Claude Fable 5.1 <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.

1 participant