Phase 10: organization & polish — cross-doc search, tags, touch, warm failures

Cross-document FTS5 search (trigram tokenizer for EN + space-free CJK, kept in
sync by triggers, back-filled from existing docs). GET /api/search uses the FTS
index for queries >=3 runes and a LIKE fallback for 1-2 (so 2-char Chinese words
resolve); snippets are built in Go with rune-aware boundaries and sentinel
highlights.

Tags: user-scoped tags + document_tags join (both cascade), idempotent
create/assign, per-tag doc counts. Doc list and search carry each doc's tags
(one tagsByDoc query). Frontend: useTags, TagChip/TagPicker/SearchBox, rewritten
DocList with chips + filter bar + search.

Tablet/touch: responsive sidebar drawer (hamburger + scrim <768px), coarse-
pointer tap targets, tap-to-open + outside-pointerdown-close for suggestion
cards.

Warm LLM-down state: useCheckpoint llmDown flag drives a gentle bilingual
StatusBar note (writing still saves locally).

Migration 0004 (tags + FTS). Tests: tags lifecycle, search EN/CJK/LIKE/update-
reindex. go build/vet/test, tsc, vite all clean; verified live on deployment host.

Claude-Session: https://claude.ai/code/session_016Yr6jELuRc7hyzYLccQKZd
This commit is contained in:
prosolis
2026-06-26 06:29:56 -07:00
parent 60eba25fee
commit 9e141e4169
21 changed files with 1841 additions and 51 deletions

View File

@@ -282,6 +282,102 @@ button, a, input {
50% { opacity: 1; }
}
/* --- Organization & search (Phase 10) --------------------------------------
Tag chips, the search results list, and per-row affordances. The row actions
(tag / delete) fade in on hover for pointer users but are always visible on
touch (no hover) — see the coarse-pointer block below. */
.petal-tag-chip {
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
}
.petal-search-results {
animation: petal-suggestion-in 160ms ease both;
}
.petal-tag-picker {
animation: petal-suggestion-in 160ms ease both;
}
.petal-row-action {
opacity: 0;
transition: opacity 160ms ease, background 160ms ease;
}
.group:hover .petal-row-action,
.petal-row-action:focus-visible {
opacity: 1;
}
.petal-row-action:hover {
background: var(--color-surface-alt);
}
/* Clamp a search snippet to two lines. */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* --- Touch & tablet polish --------------------------------------------------
On coarse pointers (tablets, phones) there's no hover, so make tap targets
comfortable and reveal affordances that would otherwise be hover-only. */
@media (pointer: coarse) {
.petal-tap {
min-height: 44px;
}
.petal-tap-sm {
min-height: 36px;
}
/* Row actions can't rely on hover — keep them visible and roomy. */
.petal-row-action {
opacity: 1;
height: 36px;
width: 36px;
}
/* Bigger, easier-to-hit suggestion/word/tag pills. */
.petal-tag-chip {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
}
/* --- Responsive sidebar (narrow screens) ------------------------------------
Below the tablet breakpoint the sidebar becomes an overlay drawer toggled by a
hamburger in the header, instead of a permanent column. A scrim sits behind it.
On wide screens the toggle + scrim are hidden and the sidebar is in-flow. */
.petal-sidebar-toggle {
display: none;
}
.petal-scrim {
display: none;
}
@media (max-width: 768px) {
.petal-sidebar-toggle {
display: inline-flex;
}
.petal-sidebar {
position: fixed;
top: 48px; /* below the 12-height header */
bottom: 0;
left: 0;
z-index: 30;
background: var(--color-bg);
box-shadow: var(--shadow-soft);
}
/* On mobile the sidebar is hidden by default; the drawer-open class slides it
in. (Distraction-free's petal-sidebar-hidden still wins to keep it closed.) */
.petal-sidebar:not(.petal-drawer-open) {
width: 0;
transform: translateX(-24px);
opacity: 0;
pointer-events: none;
}
.petal-scrim.petal-scrim-show {
display: block;
position: fixed;
inset: 48px 0 0 0;
z-index: 20;
background: rgba(61, 46, 57, 0.18);
}
}
/* Print / Save-as-PDF: strip every bit of app chrome and editing decoration so
only the title and the writing itself reach the page. This is Petal's PDF
path — it uses the browser's own fonts, so CJK renders correctly with no