Commit Graph

24686 Commits

Author SHA1 Message Date
scheinriese
cfeb8a3638 fix(icon): theme search empty-state via --ls-primary-text-color
The "No results found" empty state rendered neutral grays in OG
(container/icon at rx-gray-08, title at rx-gray-10, subtitle at
rx-gray-08) on top of a teal picker background — out of theme.

Inject `--ls-primary-text-color` (#a4b5b6 muted teal-gray in OG) as
the themed middle step across all three rules. Layer visual hierarchy
via per-child opacity (cannot set on the container — would multiply
into children and flatten the contrast):
- icon: opacity 0.5 (decorative aid, most muted)
- title: opacity 1 (most prominent line)
- subtitle: opacity 0.7 (themed but slightly muted)

Radix themes unaffected (--lx-gray-N still wins step 1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 14:07:31 +02:00
scheinriese
ace3317721 fix(icon): theme topbar separator + drop accent focus on search
Two related tweaks to the picker topbar/search chrome:

1. The horizontal separators (`.icon-picker-separator` between tabs and
   search input, and `.search-section`/`.asset-picker-search` bottom
   border) were raw `var(--lx-gray-N, var(--rx-gray-N))` — neutral gray
   in OG, out of theme. Inject `--ls-border-color` as the middle step
   (#0e5263 in OG, a themed teal that matches the picker hue).

2. Drop the `:focus-within { border-bottom-color: var(--lx-accent-09) }`
   accent on `.search-section` and `.asset-picker-search`. The search
   input has no validation state to communicate, and the search icon
   already brightens on focus (opacity-50 → opacity-75). The blue
   accent line was redundant chrome.

Radix themes unaffected (--lx-gray-N still wins step 1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 14:04:20 +02:00
scheinriese
6fb90c5804 fix(icon): re-mute placeholder + theme the (X) clear button
Placeholder fix: previous commit gave placeholder the same themed color
as typed text, collapsing the visual hierarchy. Restore differentiation
by keeping the themed color (--ls-primary-text-color in OG) AND adding
opacity: 0.6 — placeholder recedes against full-strength typed text,
themed in every theme.

(X) clear button fix: bg was raw var(--lx-gray-07, var(--rx-gray-07)),
glyph raw var(--lx-gray-12, var(--rx-gray-12)). In OG both fell to
neutral grays.
- bg → @apply bg-gray-07 (Tailwind chain includes
  --ls-quaternary-background-color → #094b5a themed teal in OG)
- hover bg → manual chain with --ls-border-color (#0e5263, one step
  lighter; gray-08 chain has no --ls-* middle)
- glyph color → manual chain with --ls-secondary-text-color (#dfdfdf
  themed light; gray-12 chain has no --ls-* middle)

Same treatment applied to the asset-picker (X) for consistency. Radix
themes preserved (--lx-gray-N still wins step 1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:59:16 +02:00
scheinriese
b563a4681e fix(icon): theme picker label text via --ls-*-text-color middle step
Section headers, search placeholders, and tab labels rendered as neutral
gray (rx-gray-11/12) in OG because Tailwind's gray-11/gray-12 chains
stop at `--lx-gray-N, --rx-gray-N` (no `--ls-*-text-color` step). On a
teal picker background, neutral gray reads as out-of-theme washout.

Inject `--ls-primary-text-color` (#a4b5b6 in OG — muted teal-gray) as
the themed middle step for label text:
- section-header inline styles (icon.cljs, 8 sites)
- .tab-item resting state (icon.css)
- .ui__input::placeholder for icon & asset pickers

For the brighter active-tab state and its underline, use
`--ls-secondary-text-color` (#dfdfdf in OG) — same role, lighter tone.

Radix themes unaffected: `--lx-gray-N` still wins step 1 of the chain.
Themes without either lx-gray-N or ls-*-text-color (older custom themes)
still fall to neutral `--rx-gray-N` for safety.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:54:09 +02:00
scheinriese
a115660d4b fix(icon): themed ghost-highlight outline via --ls-border-color middle step
The ghost-tile outline still rendered neutral gray (rx-gray-08) in OG
because Tailwind's gray-08 fallback chain is only two steps deep
(`--lx-gray-08 → --rx-gray-08`) — no `--ls-*` intermediate, unlike
gray-01 through gray-07.

Inject `--ls-border-color` manually as the middle step. In OG that
resolves to #0e5263 (a themed teal border, one shade lighter than the
quaternary-teal ghost bg #094b5a), giving a subtle outline that sits
in the same hue family as the tile. Radix themes are unaffected
(--lx-gray-08 still wins step 1).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:48:27 +02:00
scheinriese
724242118d fix(icon): themed ghost-highlight tile via Tailwind utility chain
The previous fallback form `var(--lx-gray-04, var(--rx-gray-04))` only
had two steps, so in OG turquoise (where `--lx-gray-04` is unset) the
ghost tile collapsed to `--rx-gray-04` (neutral hsl(0,0%,15.8%) gray)
on a teal picker background — out of theme harmony.

Switch to `@apply !bg-gray-04`, whose Tailwind chain is three steps
deep: `var(--lx-gray-04, var(--ls-quaternary-background-color, var(--rx-gray-04)))`.
OG's `--ls-quaternary-background-color` (#094b5a, a darker teal)
now wins, and the ghost tile sits in the same color family as the
picker chrome. Radix-* themes are unaffected (their `--lx-gray-04`
still wins step 1).

Outline color (`--lx-gray-08`) keeps the existing 2-step fallback
since Tailwind's chain for gray-08+ doesn't include an `--ls-*`
intermediate. A separate, larger pass would be needed to theme
outlines/foreground grays.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:45:36 +02:00
scheinriese
9ca1a71ea4 fix(icon): theme-fit hardening — scrollbar, color tokens, edge-to-edge search
Implements .claude/plans/icon-picker-theme-fit-bulletproof.md. Three
interrelated fixes that together make every picker surface render
correctly across themes (Radix-* and OG turquoise) without breaking
the 9-column grid.

1) Scrollbar opt-out (restores 9-col grid in OG)
- Add `scrollbar-color: auto` to `.cp__emoji-icon-picker > .bd` and
  `.asset-picker > .bd`. The OG theme's `:root { scrollbar-color }`
  rule sets explicit thumb/track colors, which forces WebKit out of
  macOS overlay-scrollbar mode into classic 15px-wide scrollbars.
  The 9-px overhead collapsed the row math (9 tiles × 36 + 8 gaps × 4
  = 356px) past the available 349px. With `auto` the browser returns
  to 6px overlay → 358px available → 9 tiles fit.

2) Theme-adaptive color tokens (fixes pink-section-headers + accent
   in selected states)
- 59 picker-scoped `var(--rx-gray-N)` references rewritten to
  `var(--lx-gray-N, var(--rx-gray-N))`, matching the existing
  fallback pattern used elsewhere in the file. Bare `var(--rx-gray-N)`
  was theme-agnostic; the wrapped form lets Radix themes (where
  `--lx-gray-N` is set) pick up the themed value while OG keeps the
  neutral gray as fallback. Same render in OG today, headroom for
  future theme refinements.
- 6 `var(--rx-blue-09)` references in selected states (asset picker
  image selection, text picker gallery selection) → `var(--lx-accent-09)`.
  Selected items now honor the user's accent in every theme (cyan in
  OG, blue/pink/etc. in Radix themes).
- 8 bare `var(--lx-gray-11)` inline styles in icon.cljs rewritten with
  the proper fallback. Fixes the pink-section-header bug: when a
  color swatch is picked, `.pane-section { color: var(--ls-color-icon-preset) }`
  cascades. The bare `var(--lx-gray-11)` was invalid in OG → property
  unset → section-header inherited the icon-preset color and turned
  pink/green/etc. The fallback keeps the property valid → header
  stays themed gray.

3) Structurally borderless edge-to-edge search input
- `.search-section` background switched from raw `var(--lx-gray-03)`
  (invalid in OG → transparent) to `@apply bg-gray-03` (Tailwind
  utility with full fallback chain through `--ls-tertiary-background-color`).
  Section is now themed in every theme.
- `.tabs-section` same treatment.
- `.ui__input` inside `.search-input` / `.asset-picker-search` is now
  structurally borderless: `bg-transparent rounded-none`. No more
  visible rounded-rect pill; the input is just text + cursor sitting
  on the section's themed background. Edge-to-edge feel is now real
  (theme-agnostic by construction), not faked via color matching.
- `.search-section:focus-within` and `.asset-picker-search:focus-within`
  swap their `border-bottom-color` to `var(--lx-accent-09)` to provide
  themed focus indication on the section (the input no longer has its
  own focus ring). Matches the CMD-K palette pattern.

Verified live in OG turquoise theme: 9 cols, 1 scrollbar, themed
section bg (#08404f), transparent input, cyan focus border, themed
gray headers regardless of icon-preset color. Verified across All /
Emojis / Icons tabs and the reaction picker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 13:40:02 +02:00
scheinriese
8b90e11fc3 fix(icon): restore has-virtual-list fixed height to prevent picker collapse
The previous single-scroller commit (3364f7bdbf) removed `h-[358px]` from
`.pane-section.has-virtual-list`, assuming `:custom-scroll-parent` alone
would carry the layout. It doesn't: when the popup container provides no
max-height (e.g., reaction picker under a block toolbar / comment thread,
or full picker opened fresh on the Emojis/Icons tab), the chain collapses
to zero — Virtuoso hasn't rendered yet → parent has no content → Virtuoso
measures 0 viewport → renders 0 items → never resolves. The picker shrunk
to ~86px tall with the entire grid missing.

The fixed height and `:custom-scroll-parent` are complementary, not
redundant:
- `h-[358px]` anchors the flex chain so Virtuoso has something to measure
  on first render.
- `:custom-scroll-parent` keeps Virtuoso from creating its own scrollbar,
  preserving the 9-column grid.

Restoring `h-[358px] overflow-y-visible` with the `searching-result h-auto`
override fixes every surface (All / Emojis / Icons / search / reaction
picker) at 442 picker / 360 .bd / 9 cols / 1 scrollbar.

Also adds defensive `(= :emoji (:type icon))` guards to the two remaining
reaction-picker call sites (block.cljs and comments.cljs) for symmetry
with content.cljs and ui.cljs. Required because comments.cljs didn't yet
require `frontend.handler.notification`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 12:28:37 +02:00
Tienson Qin
b2dee8ca75 fix: guard graph transfer concurrency 2026-05-21 18:26:59 +08:00
Tienson Qin
c48b699560 fix: prevent deleting only demo graph 2026-05-21 18:17:28 +08:00
Tienson Qin
94ccc715a1 fix: copy from comments 2026-05-21 17:49:03 +08:00
Tienson Qin
10ccc2c8c2 fix: keep search visible on iOS tabs 2026-05-21 17:48:29 +08:00
Tienson Qin
31886f8499 chore: remove tests 2026-05-21 17:31:36 +08:00
Tienson Qin
3a7c25ccde fix: don't submit comment when composing 2026-05-21 17:22:46 +08:00
scheinriese
3364f7bdbf fix(icon): single-scroller layout — restore 9-column grid across all picker modes
The reaction picker and the full picker's Emojis/Icons tabs all nested
two scrollbars (outer `.bd` + inner Virtuoso scroller). The pair ate
~12px of horizontal space, collapsing the 9-column grid to 8. The
reaction picker additionally rendered a redundant "Emojis · N" header
above its Virtuoso-Header-hosted "Recently used" section, with no
visual separator and the wrong order.

- pane-section: pass `:custom-scroll-parent` to Virtuoso unconditionally
  (was gated on `searching?`). Virtuoso defers scrolling to the nearest
  `.bd-scroll` ancestor in every mode, so `.bd` is the sole scrollbar.
- icon.css: drop the fixed `h-[358px]` on `.pane-section.has-virtual-list`.
  The pane grows to Virtuoso's reported list height; `.bd-scroll` catches
  the overflow.
- emojis-cp: render "Recently used" and "Emojis" as sibling pane-sections
  (same shape as `all-pane`) instead of nesting recents in Virtuoso's
  Header slot. Fixes header ordering and gives the two sections a natural
  divider (the Emojis section-header).
- Reaction-picker call sites (block.cljs, comments.cljs, content.cljs,
  ui.cljs): replace the inline emoji-only opts with `(merge
  reaction-picker-opts ...)` for consistency.

Verified live across every surface (All / Emojis / Icons / Custom /
search / reaction picker): exactly one scrollbar, 9 columns, correct
header order.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 11:20:53 +02:00
Tienson Qin
0a895e4d2b fix: wrong url when open another graph 2026-05-21 16:47:58 +08:00
Tienson Qin
3899511220 fix: :fix pending txs no-op during reverse 2026-05-21 16:24:24 +08:00
Tienson Qin
4ae733b65d fix: mobile toolbar action width 2026-05-21 16:23:46 +08:00
Tienson Qin
7e2062f3ac enhance: update flashcards icon 2026-05-21 15:46:38 +08:00
Tienson Qin
3deaf7486a fix: mobile search should show normalized title 2026-05-21 15:34:54 +08:00
Tienson Qin
ec0c01fd97 enhance: use graph identity in URLs (#12693)
* enhance: use graph identity in URLs

* fix: resolve graph identity URLs

* fix: satisfy graph url lint

* fix: resolve graph id from hash urls

* fix: preserve tab graph on reload

* fix: initialize tab graph before render

* enhance: open graph in another tab

* fix: open graph tabs by graph id

* fix: open local graph tabs by registry id

* fix: avoid duplicate rtc graph id

* fix: open electron graph window on shift click

* fix: address graph identity review
2026-05-21 14:52:28 +08:00
Tienson Qin
c5c24d2b6b fix: stabilize cmdk page result test id 2026-05-21 13:47:00 +08:00
Tienson Qin
0a28fb40ce fix: share block unique title for search (#12695) 2026-05-21 13:07:19 +08:00
megayu
d130d72579 Fix external asset rendering and journal import namespace handling (#12673)
* fix render external asset fail

* fix: handle nil stat in exteranal asset size calculation

* fix: normalize journal UUIDs and prevent namespace creation for slash-formatted journals

* fix: update journal UUID generation and prevent namespace creation for slash-formatted journals

* fix(import): avoid namespace pages for slash journal refs

* fix: clarify journal uuid option docs

Agent-Logs-Url: https://github.com/logseq/logseq/sessions/d8292bbe-fc6f-4c74-91cd-5705571a89b2

Co-authored-by: tiensonqin <479169+tiensonqin@users.noreply.github.com>

---------

Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tiensonqin <479169+tiensonqin@users.noreply.github.com>
2026-05-21 12:04:02 +08:00
megayu
b1008c281f enhance(page): improve navigation handling for mobile and app (#12668) 2026-05-21 11:56:53 +08:00
Tienson Qin
f791ecc45b publish style tweaks 2026-05-20 18:16:59 +08:00
Tienson Qin
bb04428527 update publish version 2026-05-20 18:05:40 +08:00
Tienson Qin
ed3be1ab07 fix: skip comments in publish payload (#12694)
* fix: skip comments in publish payload
2026-05-20 17:45:41 +08:00
Tienson Qin
6b16efc2b6 fix: avatar initials for non-latin usernames (#12692) 2026-05-20 17:21:18 +08:00
Tienson Qin
469bb1825c fix: improve mobile flashcards layout 2026-05-20 16:36:43 +08:00
Tienson Qin
93d2bf1778 fix: add mobile selection comment action 2026-05-20 16:34:56 +08:00
Tienson Qin
9b548a1a36 enhance(mobile): add language setting 2026-05-20 12:14:57 +08:00
Tienson Qin
7950eb1561 fix: upload map form sync txs 2026-05-20 11:42:57 +08:00
Tienson Qin
07e81c7511 fix: AppImage CLI launcher popup (#12686)
fix: keep appimage cli launcher stable
2026-05-20 11:32:35 +08:00
Tienson Qin
910a76fee0 fix: sync migration txs (#12687)
* fix: sync migration txs

* fix: repair missing migration built-ins
2026-05-20 11:31:06 +08:00
scheinriese
d48eea2629 style(icon): breathing room between search input and first content row
Add `pt-1` to `.cp__emoji-icon-picker > .bd` so the icon grid (and the
"Recently used" header) doesn't sit flush against the search input.
Asset-picker already had `py-1` on `.bd`, but its avatar-customize
banner negates that margin to keep the expanded-state gradient flush
against the topbar — add a 4px gap to the first `.pane-section`
following the banner so the grid doesn't read crammed against the
banner's bottom border.
2026-05-20 03:39:36 +02:00
scheinriese
e967718a3d fix(icon): unify padding strategy across picker surfaces
Two structurally-distinct dropdown surfaces leak the popup's p-1 padding
in different ways, so they need different fixes:

- Top-level (shui/popup-show! → ui__dropdown-menu-content): icon-search
  is a direct child, and its outer class swaps with @*view
  (cp__emoji-icon-picker / asset-picker / text-picker). Apply `-m-1` to
  all three view classes so switching tabs via "< Back" doesn't jump.

- Sub-content (block-context-menu "Set icon" / "Add reaction"): the
  caller wraps the picker in an extra `[:div.p-1]`, stacking two p-1
  layers. Pass `{:class "!p-0"}` to the sub-content (matching the
  precedent at icon.cljs:4615-4625) and keep the inner wrapper for
  breathing room.

Also drop the orphan `.ui__dropdown-menu-sub-content .cp__emoji-icon-picker
{ -m-2 }` rule that would overshoot under the new `!p-0` math.
2026-05-20 03:29:44 +02:00
scheinriese
06540afa27 feat(icon): emoji-only minimal picker for reactions
Reaction pickers (comments + block reactions + editor toolbar +
context menu) only need a search input and an emoji grid — tabs are
redundant (only emoji allowed), color picker is meaningless (emojis
aren't tintable), trash button has no icon to delete.

Two fixes wrapped together:

1. `:tabs [[:emoji ...]]` was unused — `icon-search` filters by
   `:allowed-tabs`, not `:tabs`. Switching reaction call sites to
   `:allowed-tabs [:emoji]` actually hides Custom/Icons/All tabs
   from the strip.

2. Add a `:hide-topbar?` opt. When true, the whole `.tabs-section`
   (tab strip + color picker + trash button) and the separator
   below collapse — the picker becomes search input + emoji grid.
   The invisible `tab-observer` and `keyboard-nav-controller` lift
   out of the topbar div so they keep working regardless. The
   `.content-pane`'s `role="tabpanel"` + `aria-labelledby` are
   gated on topbar visibility — no tab to label means no tabpanel
   role.

Reuse the existing-but-orphan `:icon/search-emojis` key (already in
24 language dicts) as the placeholder + aria-label when topbar is
hidden. No new translation work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:51:06 +02:00
scheinriese
e3d9c54651 Merge remote-tracking branch 'origin/master' into feat/unified-icon-picker
# Conflicts:
#	deps/db/src/logseq/db/frontend/schema.cljs
#	src/main/frontend/components/block.cljs
#	src/main/frontend/components/content.cljs
#	src/main/frontend/components/selection.cljs
#	src/main/frontend/worker/db/migrate.cljs
2026-05-20 02:17:43 +02:00
scheinriese
7cbc9ab65c fix(icon): polish asset-search results — recents filter, color cascade, outline ring
Three related polish fixes for the icon-picker asset search:

1. Exclude :image from the icon-picker's "Recently used" section. The
   data still lives in :ui/ls-icons-used-v2, but rendering :image
   alongside icons/emojis creates broken `photo-off` tiles (assets
   that were deleted/moved) and conceptually doesn't fit — image
   recall has its own home in the asset-picker's "Recently used
   assets" row. Filter applied at both render sites: compute-flat-
   items and the parallel all-cp path.

2. Drop the `.pane-section` wrapper class from the assets-search-
   section. `.pane-section` sets `color: var(--ls-color-icon-preset)`
   to tint Tabler SVGs via currentColor — useful for icons/emojis,
   but it polluted the image tiles' hover state: the undefined
   `--rx-accent-09` border-color rule fell back to currentColor,
   which inside .pane-section resolved to the user's picked icon
   color. Bypassing .pane-section restores the asset-picker's
   neutral cascade.

3. Switch image-asset-item + web-image-item from `border` to
   `outline` for hover/focus/selected states. A `border` with
   `border-radius` + `overflow-hidden` compressed the inner image's
   visible radius below the outer border's curve, making rounded
   corners look mismatched between the tile and its image. Outline
   draws outside the element without compressing inner content and
   follows border-radius automatically, so curves stay consistent
   across all shapes (rounded, circle, rounded-rect).

Drive-by: the outline shorthand needs an explicit `currentColor`
fallback inside var() because shorthand parsing fails entirely when
the referenced custom property is undefined (which `--rx-accent-09`
is in this codebase). Without the fallback, outline-style reverts
to its initial `none` and the ring doesn't render at all. The
prior `border-color` longhand didn't have this problem because
longhand var() invalidation falls back to the property's own
initial value (currentColor), which still renders.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 02:04:13 +02:00
scheinriese
0d7020abc1 feat(icon): asset search results in icon-picker
The search input already promised "Search emojis, icons, assets" but
only delivered emojis + icons. Add an Assets section to search results
that surfaces local image assets matching the query, so users can pick
an existing image directly from the icon-picker without drilling into
the asset-picker view.

Layout matches the asset-picker exactly: 64x68px tiles in a 5-column
grid, below the Icons section. Section is hidden when no assets match.

Reuses image-asset-item with a new :variant :search opt:
- Tooltip kept (filenames are unreadable on 64px tiles), shortened
  to 200ms delay for fast search intent vs 400ms browse intent
- Ghost-retry icon hidden in search context (broken tiles just look
  empty rather than show an ugly refresh affordance); the silent
  retry logic still runs underneath
- Animation transitions dropped to avoid jank during rapid typing

Implementation notes:
- search-assets filters a per-picker-session cache (::loaded-assets,
  loaded once on mount via sync + async paths mirroring the asset-
  picker). No DB query on keystroke.
- compute-flat-items adds an Assets section with :cols 5; the existing
  move-grid-highlight already supports per-section column counts so
  arrow-key nav across 9-col icons → 5-col assets works for free.
- Kbd shortcut extended: Alt+Meta+4 toggles the Assets section
  (extending the existing 1/2/3 pattern).
- Avatar-fallback sub-picker opts out via new :no-assets? true — its
  job is picking an icon/emoji as a fallback glyph, not an image.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:54:48 +02:00
scheinriese
871de7eed1 perf+fix: bounded-memoize hot-path, storage try/catch, tab-items button type
Three small fixes from the same review pass:

- colors/bounded-memoize: hits were O(n) because the LRU promote
  rebuilt the order vector via (vec (remove …)). Replace LRU with a
  plain memo cache that drops half on overflow. Hits become O(1)
  map lookup. The working set for adjust-for-contrast and muted-tint
  (~200 entries) stays under the 256 cap in normal use, so eviction
  is rare and the LRU bookkeeping was pure overhead.

- handler/icon-color: add-recent! and clear! now wrap their
  storage writes in try/catch like get-recents already does.
  Safari private-browsing and QuotaExceeded would otherwise throw
  from React effect cleanups; the recents list is a nice-to-have,
  silent drop is the right failure mode.

- ui/tab-items: add :type "button" so a tab can't accidentally
  submit a form. Brings parity with the sibling segmented-control
  fn. No current call site puts tabs inside a form, but cheap
  safety belt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:53:49 +02:00
scheinriese
b9fd1583a7 docs(icon): replace FIXME with root-cause explanation for filtered tabler icons
The "FIXME: somehow those icons don't work" comment hid a real
diagnosis: csk/->Camel_Snake_Case lowercases consecutive caps like
"AB" to "Ab", so the label round-trips back to "IconAb" instead of
the actual "IconAB" tabler export. The renderer's reverse lookup
misses, the icon renders empty.

Document the mechanism, why the filter is correctly scoped to the
three affected names, and why other consecutive-cap exports
(IconEPassport, IconSTurnDown) don't need the same treatment.

The real fix would store the original tabler key alongside the
label so lookup doesn't go through CSK at all, but that's a
rendering-path refactor; the filter is the right local stopgap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:33:59 +02:00
scheinriese
85cf295edc refactor(icon): extract placeholder-hex constant for color picker
Two no-input surfaces in the color picker pane used the same demo hex
(#a1b2c3 / #A1B2C3) — the hex input's placeholder ghost text and
react-colorful's SV pad starting position. Inconsistent casing on
two literals separated by ~60 lines.

Extract as a private constant with a docstring explaining the demo-
value intent. Canonicalize on lowercase to match the rest of the
codebase's hex convention; the input's placeholder renders visually
identical either way since browsers don't case-discriminate hex.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:28:52 +02:00
scheinriese
fc2f683dc6 fix(lightbox): guard against rapid double-click + clarify Escape docstring
Two related cleanups for the lightbox extension.

1. Rapid double-click guard. preview-images! did not check whether a
   lightbox was already open before creating a new one. Two synchronous
   clicks each created a lightbox + attached window listeners, then the
   second assignment to window.photoLightbox orphaned the first instance
   — its swallow filter and Escape handler stayed bound, soft-breaking
   outside clicks until reload. Early-return if pswp is active.

2. Docstring fix. Previous wording claimed the Escape handler is
   "identical to PhotoSwipe's own" when no popper is open. It isn't —
   it stopImmediatePropagation's first, which preempts other global
   Escape hooks (notably :editor/escape-editing). That behavior is
   actually desired so closing a preview doesn't also exit block-edit
   mode. Reword to match reality.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:18:22 +02:00
scheinriese
b698957e23 fix(a11y): mark avatar image as decorative with explicit empty alt
shui/avatar-image was called without an :alt prop, which leaves the
underlying <img> with no alt attribute — some screen readers fall
back to announcing the URL or filename.

The avatar is always rendered next to its label (page/block title),
which the screen reader already announces. Passing the title as alt
would double-announce; the right call is explicit empty alt to mark
the image as decorative per WCAG.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:07:39 +02:00
scheinriese
546cdfc6ee fix(icon): wire WAI-ARIA tab/tabpanel linkage on icon-picker
The tab strip already had role=tab + aria-selected + roving tabindex,
but the content area lacked role=tabpanel and ids, and the tabs lacked
aria-controls. Screen readers couldn't associate the panel with the
active tab label.

ui/tab-items: add :tab-id-prefix and :panel-id opts. When set, each
button emits id=\"<prefix>-tab-<id>\" and aria-controls=<panel-id>.
Defaults to nil so other callers (none today) stay byte-identical.

icon-search panel: id=\"icon-picker-panel\", role=\"tabpanel\",
aria-labelledby pointing at the active tab's id (dynamic via @*tab).
No tabindex=0 on the panel — its content always has focusable
descendants so an extra stop would be redundant per APG.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 23:02:45 +02:00
scheinriese
800ce16da9 refactor(icon): extract icon-data-for-storage helper
The cond that strips a picker-emitted icon down to persistable fields was
duplicated across block.cljs (page-title), property/value.cljs (default-
icon-row), and views.cljs (table row picker). Three near-identical
branches in three places.

Extract to `icon-component/icon-data-for-storage` — a pure fn paralleling
`normalize-icon` but for write paths instead of render paths. Each call
site collapses to a single function call.

Drive-by: property/value.cljs's image branch previously dropped `:id`
where block.cljs kept it. The unified helper uses the more complete
shape, which is harmless for existing image storage (extra field) and
matches what the other write paths emit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 22:53:12 +02:00
scheinriese
8efba90795 fix(pipeline): gate asset unlink on fresh local delete txs only
The renderer's fire-and-forget <unlink-asset hook fires for any tx
carrying deleted-assets, including:
- bulk graph sync downloads (:sync-download-graph?) — another device's
  retraction would wipe the local copy of an asset we may still want
- RTC remote applies (:rtc-tx?, :rtc-download-graph?)
- undo/redo replays (:undo?, :redo?)
- multi-tab fan-out (every connected tab's renderer received the same
  deleted-assets payload and raced to unlink the same path)

Add a tx-meta gate so the unlink only fires when the deletion is a
fresh, local user action. The bulk-sync case is the load-bearing one
(real data-loss risk). The undo/redo flags match codebase convention
elsewhere but note that real undo-preserves-asset support needs a
trash-folder pattern, not just a gate — flagged with a TODO.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 22:43:41 +02:00
scheinriese
0334d162ef fix(selection): atomic batch icon writes via single outliner transact!
The per-block doseq path (avatar/text/clear) used to issue N independent
transactions through the worker — partial mid-loop failures left the
selection in a mixed state, no shared undo step, and N reactive re-renders.

Wrap the doseq in one ui-outliner-tx/transact! with op-tag
:set-block-properties. The inner handler calls each open their own
transact! but short-circuit when an outer binding is active (per the
macro at frontend/modules/outliner/ui.cljc), so all N ops collapse into a
single atomic DB transaction, one undo step, and one re-render cascade.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 22:26:21 +02:00