Commit graph

31 commits

Author SHA1 Message Date
SVM0N
8518dd7a2f feat: inline csv-view block, image columns, kanban grouping fixes
Inline view (src/inline-view.ts): a csv-view code block renders an editable
table/cards/kanban view of a CSV inside any note. InlineCardHost satisfies the
renderers' duck-typed surface so renderTable/renderLibrary/renderKanbanGenre are
reused unchanged; edits write back via vault.modify and re-sync off the modify
event. Block directives: file/mode/height/collapse.

- Cards/Kanban thumbnails: getImageCol() auto-detects Image/Cover/Poster/…
  columns; resolveImageSrc() handles vault paths, wikilinks, md-images, URLs.
- Card title + context-menu "Open entry" now open the expander even without a
  notes column (NoteExpanderModal hides its notes section when absent), so
  notes-less files (e.g. applications trackers) are editable from cards.
- collapse: directive + fileCfg.collapsedGroups: Cards groups collapse by
  default and remember manual toggles (full-page too).
- Habit add-form toggles for 0/1 columns; pseudo-categorical columns (few
  distinct values) get option dropdowns in the add + edit forms via shared
  looksCategorical() helper.
- Kanban: empty group value → "—" column (was dropped in default grouping);
  rows with blank/unknown status render ungrouped instead of vanishing while
  the column header still counted them.
- Dark-mode table row hover: explicit text-tint overlay (was near-invisible).
- Docs: csv-view syntax in README; architecture + open follow-ups in handoff.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 17:53:11 +00:00
SVM0N
5011fe2433 feat: project-tag notes, Anki sync, file-template commands, Tasks refinements
Notes
- openOrCreateNotes now tags spawned notes with the row's project on
  tasks/projects files: `tags: [project-<slug>]` via a new tagify() helper,
  reviving the old per-project dashboard tag-scan. Gated to hasTaskColumns
  files so books/movies notes don't grow project tags.

Anki sync (src/view/anki.ts)
- New 🎴 Anki toolbar button (+ ⋯ menu) pushes rows to Anki via AnkiConnect
  (127.0.0.1:8765). One row → one Basic note: configurable front column
  (⚙ Columns → "Anki card front", FileConfig.ankiFrontCol), other columns on
  the back, HTML-escaped. Deck = file basename, auto-created. allowDuplicate
  off → idempotent re-sync. Clear notice when Anki isn't reachable.

Palette commands
- "Create tasks/travel/habit tracker file" scaffold a new CSV with that view's
  canonical columns, pin defaultMode, and open it (FILE_TEMPLATES +
  createTemplateFile + PromptModal).

Tasks view + Add-entry modal
- Split the Tasks view into three peer sections: Tasks / Notes / Ideas.
- Add-entry modal: Type and Priority render as native <select>s prefilled with
  Task/Note/Idea and Low/Medium/High on tasks files; Due gets a date picker
  (isDateCol now matches due/deadline). Native selects fix the focus-trap bug
  where the body-appended picker bounced focus back to the title, leaving
  empty project/type/etc. columns unfillable.

Docs + tests
- README Anki-sync section + setup, updated feature/mode notes; architecture
  module map. New coverage: tagify, anki sync (4), 3-section split, date-col
  due/deadline. Full suite green (csv 6, logic 115, mobile 21, smoke 62).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 16:47:28 +00:00
SVM0N
08db8e791b fix(css): center select labels via fixed height, not vertical padding
First pass at this used appearance:none + vertical padding + line-height,
which made the label taller than the control and clipped it at the top.
Mirror how Obsidian's own dropdowns center: a fixed `height` with zero
top/bottom padding lets the native control flex-center the label
(line-height is irrelevant). Keeps the custom chevron and per-class
horizontal padding.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:33:19 +00:00
SVM0N
cad6333949 fix(css): center <select> labels across the plugin
Styled native <select>s rendered their label vertically off (top
clipping the border) on macOS/Electron — a long-standing wart on the
view-mode picker, the library/tasks filter selects, and the per-file
config modal. Strip the native appearance so CSS box centering applies,
draw our own chevron in place of the native arrow, and switch the
per-class fills to background-color so the shared chevron image isn't
reset by the background shorthand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:28:12 +00:00
SVM0N
8ee8a98788 feat(tasks): name click opens the entry overview, not a file
Clicking a task/note name now opens the expander overview modal (same as
tapping a kanban card title) — a quick read/edit with no filesystem
touch. The 📄/+ icon is the only thing that opens/creates the backing
.md. Falls back to open/create-page when the file has no notes column to
host the expander body. +2 smoke cases (57 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:08:44 +00:00
SVM0N
084ec8ea3a test(tasks): smoke coverage for the Tasks view
Six jsdom cases over src/view/tasks.ts: task/note split + project
grouping, done→priority→due sort order, overdue flagging, the
vocab-aware done toggle, the no-type-column "all tasks" path, and the
hasTaskColumns availability gate (incl. rejecting a genre "Type" column).
Full suite green: 112 + 55 + 21 + 6, 0 failures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:55:33 +00:00
SVM0N
3dba5bc583 feat(tasks): refine — vocab-aware done toggle, click-to-edit priority, docs
- resolveDoneWord: the done-toggle reuses the file's existing finished
  word (Completed/Finished/…) instead of forcing "done" into the column
- priority cell is click-to-edit via showSelectPicker (file's own values
  + high/medium/low); re-sorts on change
- editable-cell hover affordance
- README view-modes table (Eight → Nine) + handoff follow-up entry,
  including the phase-2 cross-vault aggregate TODO

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:43:23 +00:00
SVM0N
f961aa0fb3 feat(tasks): native CSV-backed Tasks view (phase 1)
A project dashboard that renders a tasks CSV directly, replacing the
fragile DataviewJS-over-index-pages pattern. One row = a task/note/idea/
reference; grouped by a project column into a Tasks section (sorted
done → priority → due, overdue highlighted) and a Notes & Ideas section.
Each row opens/creates an optional backing .md page, reusing the Library
view's notes infra.

- src/view/tasks.ts: renderer + column resolution + hasTaskColumns gate
- wired into ViewMode, availableModes (gated on due/priority/type cols),
  render dispatch, and the onLoadFile mode guard
- taskProjectFilter / taskTypeFilter instance state
- styles + sample-data/tasks.csv

Cross-vault aggregate mode left as a phase-2 TODO in the renderer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 12:41:08 +00:00
SVM0N
0b1f89f9a8 feat: Cards/Kanban/Focus/Stats available on all files that can render them (auto-picked fallback group column)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 19:29:34 +00:00
SVM0N
1db20ed866 feat(toolbar): view-mode dropdown; Default-view picker driven by availableModes (adds missing Travel, drops unrenderable modes)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 19:20:21 +00:00
SVM0N
7c396b21c1 feat: v1.5.0 — kanban group-by selector, library sort, dup-title hint, mobile-template dedup
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-12 19:12:00 +00:00
SVM0N
bfeb183e58 feat: v1.4.0 — multi-select picker, sticky table header, csv-random block, palette commands
- Multi-select for list columns (Category/Genres/Tags/Themes/Topics, by
  name via isMultiValueColName): the picker becomes a toggle list with
  ✓ checkmarks, live-commits the comma-joined string on every toggle,
  stays open until Done/Esc/outside-click, and splits comma-joined data
  values into individual options. Wired at all five call sites (table
  cells, kanban/focus chips, add-entry + expander modals). Closes the
  "Multi-value select for Category" backlog item.
- Sticky table header: table mode reuses --no-yscroll so the table
  wrapper becomes the y-scroller (sticky needs a scrollport — the
  wrapper's implicit overflow-y:auto made sticky a no-op before).
  Header rule drawn as box-shadow since border-collapse drops sticky
  borders. Scroll restore now snapshots the wrapper's scrollTop.
- csv-random code block: a random entry card (quote/word of the day)
  for daily-note templates, with attribution, source label, and a ↻
  re-roll button. file:/field: options, same path resolution as
  csv-add. Duck-typed TFile check so it's testable with a stub vault.
- Stats bars click through: status/category bars jump to the library
  pre-filtered to that value.
- Palette commands: "Add entry to current CSV" and "Cycle view mode"
  (availableModes extracted from the toolbar as the single source of
  truth), both hotkey-able.

8 new smoke tests (38 total; 156 across all suites). jsdom env gains a
scrollIntoView no-op polyfill.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:53:44 +00:00
SVM0N
4d62437b56 feat(travel): v1.3.0 — clickable countries, detail panel, current-stay banner
Country selection: map shapes, Countries-table rows, and timeline
segments are all click targets. Selecting highlights the country on the
map (.cp-selected), dims other countries' timeline segments, and opens
a detail panel under the map listing every trip there (confirmed +
photo-inferred, newest first, with city/visa). Re-click, ✕, or clicking
an unvisited country clears. Selection lives in a renderTravel closure —
no view state, resets naturally on re-render.

Stats upgrades:
- "📍 Currently in X — since DATE (day N)" banner when today falls
  inside a confirmed trip. Blank date_left counts as ongoing; partial
  dates (2022-06-??) deliberately don't, so historic undated rows can't
  show a stale location. currentStay in travel-data, 5 unit tests with
  a fixed today.
- Cities and Longest-trip stat tiles.
- Timeline years get "Nd · M countries" summaries (confirmed days
  clipped to the calendar year).

4 new smoke tests for the selection flow. Docs updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:41:59 +00:00
SVM0N
6f925aa6ce feat: v1.2.0 — Stats + Focus views, table column sort, library UX fixes
New views (both for non-date, non-travel files):
- Stats: pure-CSS bar charts — status/category breakdowns, rating
  histogram + average, entries per year, top authors. No Chart.js;
  respects the toolbar search. src/view/stats.ts.
- Focus: one entry at a time with big typography, prev/random/next
  nav and arrow-key navigation. Gives quotes/dictionary files
  (previously table-only) a purpose-built reading view; notes render
  as markdown via a new CardView.renderMarkdownInto helper.
  src/view/focus.ts.

UX improvements:
- Table headers are click-to-sort (asc → desc → off) with numeric-aware
  comparison and empties-last; session-only, the persisted date toggle
  remains the default. Comparator extracted as sortRowsByColumn in utils.
- Library search now matches all fields — "search by author" worked in
  kanban/table but silently failed in the library (title-only).
- No-results empty states in library/kanban/focus get a Clear
  filters/search button instead of a dead end.

Both new modes are offered in the per-file default-view dropdown.
11 new test cases across the smoke suite (26 total). Bundle +8 KB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:13:49 +00:00
SVM0N
a10ba85766 docs: add README; v1.1.0 (travel view, residency counters, modular src)
- README.md: features, the six view modes + auto-detection, the flat travel
  CSV format, the residency-rule editor (with field reference), install, and
  the src/ layout + test surface.
- Bump manifest + package version to 1.1.0 and broaden the manifest
  description to mention the dashboard / library / travel views.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 06:14:38 +00:00
SVM0N
e920deb9e5 refactor(view): extract generateMobileFiles into src/view/mobile.ts
Mobile-dashboard generation moves out as generateMobileFiles(view); the toolbar
calls it directly and the now-unused mobile-templates import is dropped from
main.ts. 1 smoke case (habit dashboard written to Mobile/<file>.md with a
mocked vault).

main.ts 857 → 767. tsc clean; test:all green (view 15).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 15:35:47 +00:00
SVM0N
d7362ffc7d refactor(view): extract dashboard into src/view/dashboard.ts
renderDashboard + renderHabitTimeline (~490 lines) move out, taking the loadChart
lazy Chart.js loader with them — so non-dashboard sessions still never load
Chart.js, and main.ts no longer carries the chart plumbing (keeps only the
ChartType import for the chartInstance field + its destroy on unload). Widened
selectedDate/selectedHabit/chartInstance/timelineYear + getDateCol/
getBooleanColumns/formatDate/isTruthy/parseDate to public.

Smoke: 3 cases (full render incl. post-chart stats, per-habit timeline, empty
state) using a chart.js stub aliased in the bundler (jsdom has no canvas ctx).

main.ts 1354 → 857. tsc clean; test:all green (view 14).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:18:13 +00:00
SVM0N
e6bdd7a1dc refactor(view): extract renderToolbar into src/view/toolbar.ts
The toolbar (mode buttons, search bar, sort toggle, Columns/Mobile/Backup/
+Add/⋯ actions) moves out as renderToolbar(view, root). Widened saveFileCfg/
renderViewPreservingScroll/autoDetectBooleanColumns/generateMobileFiles/
backupToArchive/openAddModal/hasDateColumn/isTravelFile to public. toolbar.ts
declares its own __BUILD_TIME__; the smoke loader passes a define for it.
1 smoke case (mode buttons + search + row count + Add).

main.ts 1548 → 1354. tsc clean; test:all green (view 11).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:13:19 +00:00
SVM0N
3b67ed0ee6 refactor(view): extract kanban into src/view/kanban.ts
renderKanbanGenre + its renderKanbanCard helper (incl. the inline-notes editor)
move out as free functions taking the view. Widened getTitle/getSubtitle/
getColumnValues to public (visibility only). 2 smoke cases (columns-per-genre +
empty state); the column case also documents that status-grouped kanban only
cards rows whose status is among the present statuses.

main.ts 1761 → 1548. tsc clean; test:all green (view 10).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:10:16 +00:00
SVM0N
0593533099 refactor(view): extract renderLibrary into src/view/library.ts
Cards-by-genre renderer moves out as renderLibrary(view, container). Widened
getCategoryCol/getStatusCol/titleKey/authorKey/resolveCol/getNotesCol/fileCfg/
renderView + libraryStatus/GenreFilter to public (visibility only). The filter
state fields stay on CardView. 2 smoke cases added (grouping + empty state).

main.ts 1999 → 1761. tsc clean; test:all green (view 8).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:07:07 +00:00
SVM0N
e60872c2a5 refactor(view): extract renderTable into src/view/table.ts
First view-renderer extraction, proving the pattern: renderTable + its
makeEditable helper move out as free functions taking the CardView instance
(type-only import → no cycle). The ~12 members they reach (getFilteredRows,
isNotesCol, openNoteExpander, scheduleSave, persistSettings, …) are widened
from private to public — visibility only, no behavior change. Dispatch calls
renderTable(this, content).

Guarded by 2 new smoke cases in test-view-smoke.mjs driven by a hand-built
view stub (no CardView/FileView instance needed). main.ts 2103 → 1999.
tsc clean; test:all green (csv 6, logic 107, view 6).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 16:02:33 +00:00
SVM0N
228b13b1c0 test: DOM smoke harness for view renderers (jsdom + obsidian stub)
The regression net for the main.ts → src/view/* split. test-support/dom-env.mjs
sets up jsdom and polyfills Obsidian's Element extensions (createEl/createDiv/
setText/empty/addClass/setAttr/… on Element.prototype, so SVG nodes get them
too). test-support/obsidian-stub.mjs satisfies the obsidian imports so view
code bundles under node (esbuild alias). test-view-smoke.mjs renders a view
into a real DOM tree and asserts structure — 4 cases over renderTravel (core
sections, residency gauge, empty state, SVG map coloring). Wired into
test:view + test:all. Adds jsdom devDependency.

Next renderers extracted should add a case here (drive with a fake `view`
stub) before committing. test:all green (csv 6, logic 107, view 4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:55:34 +00:00
SVM0N
9913bc6f17 refactor: extract csv-add entry form into src/add-entry-form.ts
renderAddEntryForm (~370 lines) only depended on this.app, so it moves out as
a free function renderAddEntryForm(app, source, el, ctx). Call site passes
this.app. Drops now-unused imports (resolvePath, MarkdownRenderer,
MarkdownPostProcessorContext) from main.ts.

main.ts 2476 → 2103. tsc clean, 107/107, build green. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 15:41:06 +00:00
SVM0N
f154dc676c refactor: extract CardViewSettingTab into src/settings-tab.ts
First step of the main.ts modularization. The settings tab + residency-rule
editor only touch this.plugin (settings + saveSettings), so they move out
cleanly with a type-only import of CardViewPlugin (no runtime cycle). Drops the
now-unused Setting/PluginSettingTab imports from main.ts.

main.ts 2569 → 2476. tsc clean, 107/107, build green. No behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 14:16:17 +00:00
SVM0N
e1f5e215e6 feat(travel): world-map view, residency gauges, editable trips, modal UX
Read-only "travel" view mode for flat travel CSVs (country + date range +
source columns): interactive world choropleth (gold=confirmed, blue=photo-only),
stats row, per-country day totals, year-by-year timeline, instant hover
tooltips, and an editable confirmed-trips table. Plus configurable residency /
threshold day-gauges with an in-app rule editor in Settings, native yyyy-mm-dd
date pickers and non-strict source/resolved dropdowns in the add/edit modals,
and a mobile-responsive layout.

Modules: src/travel-data.ts (pure analysis + country reference data),
src/travel-view.ts (render), src/residency.ts (rule evaluation),
src/field-types.ts (modal field heuristics). world-map.svg ships beside the
bundle and loads at runtime (main.js stays lean). 107 unit tests over synthetic
fixtures (sample-data/travel_flat.csv). Default residency rule is a single
neutral Schengen example; user rules live in data.json only.

History squashed from the travel-feature commits into one clean commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 14:08:10 +00:00
SVM0N
f59f099f79 feat(mobile): scroll-preserve audit + restyled × clear inside input
Two small follow-ups to the mobile search work.

Scroll-preserve audit: six in-place-edit call sites that were calling
plain renderView (which empties the content area and resets scrollTop
to 0) now go through renderViewPreservingScroll. The list:

  - deleteWithUndo: row deletion + the undo restore
  - context-menu Mark-as status change
  - AddEntryModal onSubmit
  - Table sort-order toggle
  - Dashboard habit-checkbox toggle

Sites that genuinely warrant a scroll reset (mode switch, file open,
dashboard date navigation, FileConfigModal which may change the mode)
keep plain renderView.

× clear button restored on mobile, but properly:

  - Input now lives in a relative wrapper (.csv-search-input-wrap) so
    the × can position:absolute over the input's right edge without
    affecting any sibling. The Done button no longer shifts when ×
    appears mid-typing — × never participates in layout in the first
    place.
  - Visibility flipped via an `is-hidden` class on the button (opacity
    + pointer-events: none) instead of display:none, so there's no
    even-momentary reflow on the toggle.
  - Pill styling: 22×22 circle with surface-2 background, hover bumps
    contrast. Sits inside the input on the right, indistinguishable
    pattern from any native search field.
  - × now only clears the query and refocuses (so the user can keep
    typing), not collapses the bar. Done handles bar-collapse when
    pressed on an empty input.

Behavioral summary for the mobile search row:
  [search-input(×)]  [Done]
  - Type → underlying view filters live (debounced 120ms)
  - × → clear query, keep bar open + focused
  - Done with text → dismiss keyboard, keep bar + filter
  - Done with empty input → collapse bar back to normal toolbar
  - Return key → same as Done with text

Tests: 88 + 21 green. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 23:51:34 +00:00
SVM0N
dff9f30c37 feat(mobile): inline search with Done/Return, dashboard scroll-preserve
Iterative pass on mobile search UX after the first try (modal with preview
list) felt heavier than warranted. End state is closer to the desktop
inline search but adapted for the iOS keyboard.

Mobile search flow:
- Tap 🔍 → toolbar transforms inline: mode group / + Add / ⋯ hide and the
  search input + Done button fill the row. No modal, no fixed positioning
  acrobatics, no extra layer.
- Type → underlying view filters live in whatever space iOS leaves above
  the keyboard. With the keyboard up that's small, but it's enough to see
  the result count and a row or two.
- Tap Done or hit Return → keyboard dismisses, WebView returns to full
  size, the filtered view is fully visible. Search bar stays open with
  the query, so the user can edit or close from there.
- Done is multi-purpose: with text in the input it dismisses the keyboard
  (filter persists); with empty input it collapses the search bar back
  to the normal toolbar. Single exit affordance — no separate × needed,
  which previously caused Done to shift position mid-typing.
- The Return key is relabeled "Search" via enterkeyhint, and inputmode
  is set to "search" for the search-style on-screen keyboard.

Also during this cycle:
- An intermediate SearchModal-with-preview-list approach (still exported
  in src/modals.ts for future use) which proved heavier than the inline
  approach. Left in tree as it carries the visualViewport + .modal-bg
  transparency patterns and could be useful for other things.
- "Found X of Y entries" hidden on mobile while search is open — it
  clipped weirdly when iOS shrunk the WebView and was redundant with
  the input being visible at the top.
- Build-time timestamp injected via esbuild's `define`. Surfaced as
  "Built YYYY-MM-DD HH:mm" in the ⋯ menu and as a small ⓘ button on
  desktop. Useful confirmation on iPhone that iCloud has synced the
  latest deploy.

Dashboard scroll-preserve:
- Tapping a habit card to open its timeline (and tapping ✕ / changing
  the year selector) now goes through renderViewPreservingScroll
  instead of plain renderView. Without this, opening the timeline below
  the fold yanks the user back to y=0 and they have to scroll down to
  find the card again to see what they just opened.

Bug fixes / reverts from earlier in the session:
- Reverted `will-change: scroll-position` on .csv-table-wrapper —
  intermittent iOS WebKit bug where the promoted compositor layer was
  clipped outside the parent bounds, making rows invisible. The scroll
  smoothness from the earlier cleanup (per-td ::after + position:relative
  scoped to .csv-cell--clipped) was enough on its own.
- Modal centering fix: don't override the transform (Obsidian centers
  the modal via parent flex, not a transform on the modal); use
  align-self: flex-start + margin-top: 10px on mobile so the modal
  top-anchors without breaking horizontal centering.
- Library view stopped rendering its own duplicate search input.
- Kanban notes preview gets overflow-wrap: anywhere so a URL or hash
  doesn't push the card past the column.
- Mode labels renamed: Library → Cards, By Genre → Kanban. Generic for
  non-library files; also shorter so the toolbar fits one row on mobile.

Tests: 88 plugin + 21 mobile-dashboard, all green. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 23:36:49 +00:00
SVM0N
b07bc3c4fb feat(mobile+ui): kanban slim, table-lag cuts, modal keyboard fix, search collapse
A multi-pass mobile session. Each change addresses a specific iOS report,
but they hang together because they're all the same shape of bug: the
plugin is rendered in WKWebView, which has its own opinions about
keyboards, scroll, hover, layer compositing, and table layout.

— Kanban card: bottom hover-button row removed. Title-tap opens the
  expander (already wired); small +/📄 in the title row creates or opens
  the sidecar .md. One affordance per action; recovers vertical density.

— Mobile inline-note edit routes to the expander modal instead of the
  inline textarea. iOS keyboard pop scrolls focused inputs into view,
  which yanks the user's y-position. The modal owns its own viewport so
  the keyboard resizes the modal, not the underlying card list.

— Modal keyboard handling. Two stacked fixes after the obvious ones
  didn't take:
  1. visualViewport.height (not dvh, which iOS Obsidian doesn't honour)
     written into --csv-modal-vh on resize+scroll, used in max-height.
  2. On mobile, align-self: flex-start + margin-top: 10px instead of
     overriding the transform — Obsidian centers via parent flex, not
     a transform on the modal, so translating the modal kicks it off
     the left edge. align-self leaves horizontal centering intact and
     top-anchors the modal so its sticky-bottom footer stays above the
     keyboard.
  3. .modal-content overflow-y: auto + .csv-expander-footer position:
     sticky bottom: 0 — modal scrolls if content overflows, footer
     floats above the scroll content and is always reachable.

— Mobile toolbar: search input collapses to a 🔍 toggle that overlays
  the toolbar (position: absolute, not flex displacement — displacement
  pushed Cards/Kanban/Table off the visible toolbar area and read as
  'the view disappeared'). Expanded state holds while the query is
  non-empty; empty-blur recollapses. Library renamed → Cards, By Genre
  → Kanban so the labels read correctly for non-library files and the
  toolbar fits one row.

— renderLibrary used to render its own .csv-library-search alongside
  the toolbar one — both wrote to this.searchQuery, pure duplicate.
  Removed.

— Table lag on iPhone (was the worst report — 'scrolls to notes column
  and freezes'). Three causes:
  1. Per-row requestAnimationFrame for clip detection: 200 rows × N
     cells × forced reflow each. Batched to one rAF, skipped on touch.
  2. td::after gradient pseudo on every td (opacity:0 by default) =
     N×M compositor pseudos. Scoped to .csv-cell--clipped only; on
     touch the class is never added, so mobile pays zero.
  3. position:relative on every td = N×M stacking contexts. Also
     scoped to .csv-cell--clipped only.
  4. -webkit-line-clamp on the td itself overrode display:table-cell
     and broke row borders. Moved the clamp onto the inner span so
     the td stays a normal table-cell.
  5. tr:hover gated behind @media (hover: hover) — iOS sticky-hover
     was leaving the last-touched row tinted and forcing a recomposite
     on every scroll frame. Also dropped contain: content on tbody tr;
     CSS containment on table-internal elements is spec-undefined and
     iOS WebKit was occasionally dropping rows from layout.
  6. .csv-table-wrapper gets will-change: scroll-position so iOS
     promotes it to its own compositor layer.

— Mobile table search input debounced by 120ms so each keystroke
  doesn't trigger a full content re-render. Search-focus also restores
  window scroll on the next frame — iOS WKWebView auto-scrolls focused
  inputs into view even with preventScroll, pushing the table below
  the keyboard.

— Kanban notes preview: overflow-wrap: anywhere so a URL or hash in
  the note doesn't push the card wider than the column.

— Build-time timestamp baked into the bundle via esbuild's define;
  surfaced as 'Built YYYY-MM-DD HH:mm' in the ⋯ menu + a small ⓘ
  button on desktop. Useful on iPhone where iCloud sync of the
  deployed bundle can lag and there's no native way to confirm the
  new code arrived.

— Transition durations capped: 0.3s → 0.15s on the habit progress bar,
  0.2s → 0.1s elsewhere. UI feels snappier; transitions are compositor-
  driven so shortening doesn't slow anything down.

Tests: 88 plugin + 21 mobile-dashboard, all green. Typecheck clean.

Bundle: 300 KB minified (unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 20:43:32 +00:00
SVM0N
9e7f40cba5 fix(mobile): inline note cursor + tap-vs-swipe disambiguation
Two kanban-on-mobile bugs:

(1) Tapping the inline note preview opened the editor with the caret at
the end of the textarea (default behavior after setting .value
programmatically). On long notes this scrolled the textarea's internal
viewport past all the content, so the user saw an empty area "much
down" and assumed the editor opened blank. Also: scrollHeight read
inline (before layout) returned ~0, so the height clamped to the 120 px
floor regardless of content length.

Fix: caret at position 0, scrollTop = 0, defer height calc to next
frame so scrollHeight reflects laid-out content.

(2) Swipes between scroll-snap kanban columns (and momentum scrolls
inside a column) occasionally registered as taps on the underlying
card — opening the note editor or expander modal when the user just
wanted to scroll.

Fix: installTouchScrollGuard on .csv-content-area. Tracks
touchstart→touchmove distance; if the finger moves more than 10 px
(iOS's own tap threshold) before touchend, swallows the subsequent
click in capture phase before any descendant handler runs. Pure
touchmove-based, so programmatic scrolls (e.g. the scroll-restore
inside closeInlineEditor) don't create false lockouts. Touch-only
via matchMedia gate; desktop behavior unchanged.

Single install at the content-area level covers kanban, library,
and table views.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 07:48:03 +00:00
SVM0N
ee64788f55 refactor: rename XLSXCardView → CardView
Class-internal rename. The CARD_VIEW_TYPE string registered with Obsidian
is unchanged, so the rename is purely cosmetic from the API's perspective.
Sweeps 8 sites: class def + instantiation in main.ts, plus mention-only
references in test-plugin-logic.mjs, xlsx-to-csv-roundtrip.mjs,
src/mobile-templates.ts, docs/architecture.md, docs/dev-workflow.md.

Closes the follow-up flagged in handoff.md after SWITCH TO CSV AS MAIN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 07:31:35 +00:00
SVM0N
3b213d0650 SWITCH TO CSV AS MAIN: retire XLSX, drop SheetJS, simplify save path
Plugin is CSV-only. registerExtensions(["csv"]); SheetJS gone; the
_csv_helpers/ mirror collapses to a single canonical CSV. Bundle drops
720 KB → 297 KB (-59%); parse+eval 0.9 ms → 0.5 ms.

Migration plan was empirically validated before any data was touched:
xlsx-to-csv-roundtrip.mjs reads each xlsx, Papa.unparse → Papa.parse,
cell-by-cell compares the result to the source. All 10 checks passed
across books / movies / quotes / dictionary / habit_tracker — proves
the round-trip is lossless including multiline notes, stars, embedded
punctuation, and unicode.

migrate-xlsx-to-csv.mjs is the cutover: writes canonical csv, moves
xlsx → Archive/<name>_pre-csv-migration.xlsx (recoverable), removes
the _csv_helpers/ folder, rewrites data.json fileConfigs keys from
*.xlsx → *.csv so per-file overrides survive.

Code surface:
- main.ts: onLoadFile / doSave / generateMobileFiles / renderAddEntryForm
  all collapsed to the csv-only path; loadXLSX + isXlsx removed.
- mobile dashboards: csv-add and dataviewjs now point at the same
  canonical csv (used to be split because Dataview couldn't read xlsx).
- regenerate-mobile-dashboards.mjs: drops the helper-folder path.
- package.json: xlsx dependency removed (9 transitive packages gone).

Also included in this commit (carried over from the same session):
- mobile toolbar: ⚙ Columns / 📱 Mobile / 💾 Backup collapse into a
  ⋯ overflow menu on screens ≤ 600 px so the toolbar fits on one row.
- mobile compact density: pure-CSS media-query switch for Library
  (2-col compact grid + mobile-md card style), tighter Kanban cards,
  smaller Table cells.

Tests: 88 plugin + 21 mobile-dashboard, all green. Typecheck clean.

Verified manually: open books.csv, edit a note (saves debounce), open
on iPhone (compact library grid, ⋯ menu, csv-add form pre-fills).

Class name XLSXCardView left in place to keep this commit focused on
behavior; rename to CardView is a noted follow-up in handoff.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 07:23:02 +00:00