Commit graph

31 commits

Author SHA1 Message Date
SVM0N
9ca0bf1a0e chore(release): v1.7.1 — community-plugin review fixes
Addresses the automated review's warnings: !important overrides
resolved via higher-specificity selectors (a handful of legitimate
overrides of Obsidian's own core .modal chrome are kept, since they
guard against unowned CSS loaded after the plugin), a :has() selector
replaced with a JS-toggled class, duplicate-property fallback chains
rewritten with @supports, TFile casts routed through an instanceof
fast path, and a couple of narrow, justified exceptions left as-is
(a proper-noun tooltip, a createEl helper that isn't stubbed in the
test harness). Bumped the obsidian devDependency to pick up current
types along the way.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-18 13:30:02 +08:00
SVM0N
c3900c415d fix(budget,search): category multi-value mis-routing, row alignment, mobile keyboard black-screen
Four issues from continued mobile testing:

1. Category field opened the multi-value chip picker (Search/add/Done UI)
   instead of a plain single-pick dropdown, and interacting with it could
   land on the Date field's native picker. Root cause: isMultiValueColName's
   regex matches singular "category" (not just "categories"), which routes
   the Add-entry modal into the tags-style chip picker regardless of
   categoricalColumns — that check runs unconditionally before the
   categorical branch is ever reached. Didn't touch the shared regex (it's
   deliberate for existing Kanban/Library comma-separated-tag usage);
   renamed the budget template's column to "Type" instead — still resolves
   via CATEGORY_COL_ALIASES, isn't multi-value-matched.

2. Item rows read "too tall" with inconsistent vertical alignment (name
   centered, other columns top-aligned). Tasks' shared .csv-tasks-table td
   defaults to vertical-align: top (right for its own often-multi-line
   rows) but .csv-tasks-name-cell overrides itself to flex/center — fine
   there, but jarring when every Budget row is short single-line text.
   Added a `.csv-budget .csv-tasks-table td { vertical-align: middle; }`
   override scoped to Budget's own table so Tasks is untouched.

3 & 4. Both the Limit input and the toolbar's inline mobile search caused
   a black screen when the iOS keyboard opened. Root cause (confirmed via
   an existing code comment on .csv-search-modal-list, predating this
   session): "iOS shrinks the underlying view to nothing when the
   keyboard's open." CardView has no visualViewport keyboard handling
   (unlike NoteExpanderModal/SearchModal, which pin themselves to
   visualViewport.height) — any inline input directly in the main view's
   collapsing content area hits this. SearchModal already solves it
   properly (viewport pinning + a live results-preview list rendered
   inside the modal itself) but was never wired to a trigger — it sat
   unused (I'd even removed its now-stale "unused import" from main.ts
   earlier this session without realizing why it existed). Wired it to the
   toolbar's search toggle on touch devices; desktop keeps the inline
   expand since there's no keyboard to collapse anything there. The Budget
   Limit field is now a button that opens PromptModal (given an optional
   submitLabel/inputType — "Set"/"number" here, defaults preserve the
   existing "Create X file" callers) instead of an inline number input,
   plus a direct clear "×" button for removing the limit without typing.

6 new/changed tests (clear button, limit button states, no-inline-input
assertion). 120 tests passing, typecheck/lint clean.

Note: an already-created budget file's fileConfig won't retroactively pick
up the Type-vs-Category rename — only new files via "Create budget file".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
2026-07-17 18:10:22 +08:00
SVM0N
a2da3a4892 feat: budget view — category rollups against a spending limit
New view mode: items grouped by category (reusing the same "group by"
resolution Kanban/Cards use), each row's price summed into a per-category
subtotal and a grand total, compared against a per-file spending limit set
inline in the view — the total reads blue while under, red once over.

- src/view/budget.ts: renderer + hasBudgetColumns/budgetPriceCol. The
  availability gate is deliberately name-gated (Price/Cost/Amount/Total/
  Spend/Value column, or an explicit override) rather than "any numeric
  column" — Chart's gate is that broad on purpose; Budget being that broad
  would put a Budget tab on every file with a Rating or Year column.
- Item list reuses the Tasks view's collapsible group/table shell
  (.csv-tasks-section/.csv-tasks-group/.csv-tasks-table) instead of
  duplicating that CSS.
- "Price" added as a 7th exclusive column function in ⚙ Config → Column
  functions, alongside Title/Category/Status/Notes/Image/Anki front.
- effectiveGroupCol's last-resort fallback picks any low-cardinality column
  when there's no real category — on a file with nothing else groupable
  that can be the price column itself (two rows priced "10"/"20" would read
  as two categories). Budget explicitly rejects that one case.
- parseNumeric (chart.ts, shared with Chart view) now also strips a single
  leading/trailing currency symbol ($/£/€/¥) before parsing, so "$45.00"
  sums correctly.
- 6 new view-smoke tests; 118 total, all passing. typecheck/lint clean.

Version left at 1.6.1 — not tagging a release yet, this needs a look in
the actual app first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
2026-07-17 16:50:05 +08:00
SVM0N
3c8ac06574 fix: resolve community-plugin review findings (styles, innerHTML, promises, world-map bundling)
Obsidian's automated plugin review flagged this release: inline style
mutations instead of CSS classes, unsafe innerHTML writes, async callbacks
passed where a void return was expected, and a couple of real bugs the
review's style checks happened to surface along the way.

- Replace `.style.*` assignments with CSS classes (`is-hidden` etc.); the two
  genuinely dynamic textarea-autosize resets use `removeProperty` instead.
- Replace innerHTML writes (map SVG injection, dashboard stat bars, legend,
  refresh button) with DOMParser/createEl/appendText.
- Embed world-map.svg into main.js via an esbuild text-loader import instead
  of reading it from the plugin's vault folder at runtime — Obsidian's
  installer only ever fetches main.js/styles.css/manifest.json from a
  release, so the travel map was silently broken for any Community Plugins
  install (only worked for manual/BRAT installs that copied the extra file).
- Replace window.confirm() on delete with the same two-click "Confirm?"
  button pattern already used elsewhere in the plugin (native confirm()
  doesn't behave reliably on mobile Obsidian).
- Settings tab: use Setting().setHeading() instead of raw <h2>/<h3>, drop the
  redundant plugin-name heading, wrap async onChange/click handlers so they
  don't return unhandled promises.
- MarkdownRenderChild subclasses (chart/inline-view/tasks blocks): onload()
  must be synchronous per Component's contract; move the async work inside
  instead of marking onload itself async.
- Misc: unused imports/vars, unnecessary type assertions (two of which were
  actually needed — restored with `querySelector<HTMLElement>` instead of a
  blind cast), sentence-case UI text, template-literal error interpolation
  on caught `unknown` values.
- Add eslint + eslint-plugin-obsidianmd as a permanent dev dependency
  (`npm run lint`, wired into `npm run check`) so these surface locally
  before the next review instead of after.

114 → 112 existing tests still pass (2 net-added assertions from the DOM
shim gaining appendText/DOMParser polyfills); typecheck and build clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
2026-07-17 16:29:49 +08:00
SVM0N
4f20484ff7 style(config): one scroll surface in the modal on phones
The ⚙ Config modal stacked three nested y-scrollers on mobile — the modal
content, the 60vh .csv-modal-form, and the 360px column-config table wrap
— so a swipe grabbed whichever inner region was under the finger instead
of moving the modal. Phones now let both inner regions grow to content
height; the modal is the single vertical scroller and the table keeps its
horizontal swipe. (Also applies to the Add-entry modal, which shares
.csv-modal-form.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 02:27:14 +00:00
SVM0N
95f0a24f80 style(chart): compact mobile controls — two per row, short inputs, chart gets the space
The control pills each took Obsidian mobile's full input height on their
own line, pushing the canvas below the fold (~40% of the screen). Phones
now get a two-per-row grid at 32px control height, a full-width formula
input, and a taller (300px) canvas. Smooth toggle shares the active
styling with Best fit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 02:13:30 +00:00
SVM0N
6cb2deccb2 feat(chart): Chart view + csv-chart block with best-fit and formula overlay
New Chart view mode (scatter/line of any numeric column pair, X/Y pickers
persisted per file) plus a csv-chart code block for embedding charts — or
pure y=f(x) plots — in notes. Least-squares fit line with equation + R²
(per-day trend phrasing on date X), formula overlay via a new eval-free
expression parser (src/formula.ts). Chart.js lazy loader extracted from the
dashboard into src/chartjs-loader.ts and shared by all three consumers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 15:32:30 +00:00
SVM0N
51e00499dc Optimize tasks table column widths for mobile viewing 2026-07-07 11:20:28 +00:00
SVM0N
79c4875162 Fix AddEntryModal constructor and enhance Tasks file template 2026-07-07 10:42:09 +00:00
Cursor Agent
c4189c58e1 feat(config): per-column "Clean up" action for Checkbox columns
Direct follow-up to the isTruthyVal/normalization discussion: instead
of a column only converging onto strict 1/0 one row at a time as each
happens to get toggled, add a one-shot cleanup button scoped to a
single column (not a whole-file action).

Appears next to the Type select only when that column is Checkbox-
typed. Rewrites every row's raw value in that column to exactly "1"
(isTruthyVal match: yes/true/1, case-insensitive) or "0" (everything
else - 0, "", no, false, or any other leftover text) - the same rule
already used to decide checked/unchecked, just applied to the whole
column at once. Two-click confirm, same pattern as removing a column,
since it's an immediate data rewrite (not deferred to Save) - and a
Notice reports how many rows actually changed.

- CardView.cleanupBooleanColumn() (main.ts) does the actual rewrite +
  save + re-render.
- FileConfigModal takes an onCleanupBooleanColumn callback (defaults to
  a no-op so older callers/tests are unaffected), wired through
  toolbar.ts's openColumns().

Tests: the button only appears for Checkbox-typed columns (not
Categorical/Text/the title column), and the two-click confirm gates
the actual callback correctly.

Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
2026-07-06 09:45:49 +00:00
Cursor Agent
8a4de617f6 feat(config): unify Habit+Categorical into a Type column, add Title as a Function, render as a real table
Follow-up to the auto-detected-role indicator, from further discussion
of the redesign. Two changes to the data model, one to the layout:

1. Type (Text/Checkbox/Categorical) replaces the independent Habit and
   Categorical toggles with one mutually-exclusive select. Nothing
   stopped a column from being checked as both before, which never
   made sense — a column has one data shape, not two. Still backed by
   the same habitColumns/categoricalColumns lists at runtime (no
   behavior change to getBooleanColumns/isCategoricalCol), the modal
   just now enforces the exclusivity that was implicitly assumed
   everywhere else.

   Status stays a Function, not a Type — deliberately not generalizing
   to a plain 'Checkmark' type. The Tasks-view done-checkmark comes
   from word-matching (done/completed/closed/…) against whatever
   multi-valued vocabulary a real Status column already uses, not from
   the column being strictly 2-valued; most real status columns have
   3+ states.

2. Title is now a configurable Function, backed by a new
   FileConfig.titleColumn (defaults to auto-detect by name, same as
   before). There was previously no way to override which column is
   the title at all. titleKey() in main.ts and inline-view.ts now
   check the override first.

3. The per-column config renders as an actual <table> (Column | Type |
   Function | Card field) instead of stacked vertical cards, scrolling
   horizontally on narrow widths rather than wrapping — same pattern
   the sticky Table-view header already uses.

Tests: rewrote the FileConfigModal suite for the table structure,
added coverage for Type mutual exclusivity and the new Title function.

Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
2026-07-06 09:06:47 +00:00
Cursor Agent
e260a4ed00 feat(config): surface auto-detected roles in the ⚙ Config modal
The per-column Role dropdown only ever showed an explicit fileCfg
override — a column literally named 'Status' (or Category/Notes/Image/
Anki-front by their own name lists) already drives getStatusCol() etc.
at render time by name, but the modal showed '— no special role —' for
it with nothing to indicate that. This was the exact confusion behind
a user question: 'what corresponds to the checkmark in the file?' —
the Status role does, but for a conventionally-named column there was
no visual confirmation it was already active.

- Extracted the Category/Status/Notes column name-alias lists (already
  duplicated between main.ts and inline-view.ts) into shared constants
  in src/utils.ts, and split ankiFrontCol's fallback chain out into an
  exported autoAnkiFrontCol() in src/view/anki.ts. Both were already
  pure name/position lookups; this just makes them independently
  callable without an explicit override applied.
- FileConfigModal now takes an AutoDetectedRoles snapshot (computed
  once in toolbar.ts's openColumns(), the same lists/functions the
  views themselves use) and roleOf() shows the resolved role plus an
  'auto (by name)' badge whenever nothing is explicitly configured for
  that slot — exactly mirroring the fact that explicit overrides always
  win outright over name detection at runtime (never blended).

Tests: two new FileConfigModal cases — a name-matched column shows its
role with the auto badge and no saved config, and an explicit override
on a different column suppresses the badge everywhere for that role,
matching what getStatusCol()/etc. actually resolve.

Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
2026-07-06 08:47:14 +00:00
Cursor Agent
53ca235bc2 feat(config): redesign file settings into a per-column role table
Replaces the ⚙ Columns modal's eight separate 'which column(s) for
role X?' sections (Category/Status/Notes/Anki-front dropdowns +
Habit/Card-field/Categorical checkbox grids) with a single vertical
list of per-column rows. Each row gets:

- a Role select (none/Category/Status/Notes/Image/Anki front) —
  mutually exclusive, since each of these was already a single-column
  slot; picking one for a column automatically clears it from
  whichever column held it before, and clears any other role the same
  column previously held.
- three independent toggles (Habit/Card field/Categorical) a column
  can hold any combination of.

Also folds in an Image-column picker (previously name-detection only,
no UI) and renames the toolbar button/menu entry from 'Columns' to
'Config' since it now covers more than columns.

Role/toggle edits stay in-memory (this.current) until Save, same as
before; only add/remove-column still refetches from disk immediately
since those persist right away. Split refresh() (disk refetch, for
structural changes) from a new renderRows() (local re-render, for
role/toggle changes) so editing roles never discards other unsaved
edits in the draft.

Tests: rewrote the categorical-toggle tests for the new row structure,
added coverage for role exclusivity (cross-column eviction, same-
column reassignment) and confirmed role/toggle edits never call
getFileCfg (no disk refetch).

Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
2026-07-06 08:19:57 +00:00
Cursor Agent
9a7ead1696 feat(entries): add a highlight option for entry titles
Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
2026-07-06 07:35:55 +00:00
SVM0N
992d57cdc9 rename plugin to DataDeck; fix(kanban): show empty-field chips as em dash
Renamed csv-card-view -> datadeck (id, display name, symlink, docs) to
better reflect the plugin's scope beyond a single card view. Also fixed
kanban cards silently omitting a field's chip when its value was empty,
instead of showing "Field: —" like the table/select-chip views already do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 13:41:37 +00:00
SVM0N
8d7afe4d9a fix(picker): stop internal scroll from self-dismissing dropdown; feat: add/remove CSV columns from Columns modal
The picker's capture-phase scroll listener dismissed on any scroll in the
document, including its own list — so scrolling the list, or typing (which
auto-scrolls the keyboard cursor into view), closed it instantly and bounced
focus back to the modal. Now it ignores scrolls that originate inside the
picker itself.

The ⚙ Columns modal also gained inline column add/remove: a chip list with a
confirm-to-delete ✕ per column, and a "+ Add column" input, wired through new
CardView.addColumn/removeColumn methods that update headers, row data, and
any per-file config pointing at a deleted column.
2026-07-01 10:19:36 +00:00
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
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
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
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
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
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