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>
Cropped to match the existing screenshots' convention (plugin content
only — no OS chrome, no Obsidian window/tab bar, no sidebar) and stripped
of embedded metadata: the raw capture carried an ICC display profile
(including a device Make/Model field) and XMP/EXIF timestamps in UTC that
back out to a UTC+8 capture timezone — the same class of locale/device
fingerprinting signal flagged in the project's earlier privacy pass, just
in image metadata instead of code comments. The four pre-existing
screenshots were already clean (verified via exiftool); this one now
matches.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
Root cause of the "Electronic" showing twice in a category/type picker but
only once in the grouped view: CardView.getColumnValues (main.ts) — the
shared source for every picker's distinct-values list (Add-entry dropdown,
NoteExpanderModal's chip picker via showSelectPicker, filters) — deduped
with a plain Set over raw, untrimmed row values. Grouping views (Kanban's
effectiveGroupCol, Budget's catOf) already trim their own group keys, so
two rows differing only by trailing whitespace ("Electronic" / "Electronic ")
collapsed into one group in the view but showed as two options everywhere
getColumnValues fed a picker.
Trim before building the Set. Also fixed showSelectPicker's "is this the
currently selected option" check, which compared a (now-trimmed) option
against the raw untrimmed currentValue — without trimming that side too, a
dirty row's own current value would stop matching its own trimmed option.
3 new tests on a hand-copied getColumnValues (matching this file's existing
pattern for testing main.ts-only logic) — trailing whitespace, distinct
values, whitespace-only values dropped. 121 view-smoke + 118 plugin-logic
tests passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
Price editing had two problems:
- Same collapsing-main-view issue as the Limit field/search: makeEditable's
inline <input> on iOS showed a black screen when the keyboard opened.
- makeEditable only mutates the row and rewrites that one cell's own text
on blur — it never re-renders the view, so the category/grand-total
rollups (computed once at renderBudget time) went stale after an inline
price edit until the next unrelated re-render.
Same fix as the Limit field: a PromptModal instead of makeEditable. Fixes
both — no inline input in the collapsing view, and the commit callback
calls renderViewPreservingScroll so the rollups recompute immediately.
New test asserts no inline input on the price cell (regression guard
against reverting to makeEditable there). 121 tests passing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
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
Two real bugs from mobile testing:
1. "Category renders as a broken on/off switch" in the Add-entry modal.
"" is in BOOLEAN_PATTERNS (looksBoolean), so a fresh budget file with
one row and an empty Category value vacuously auto-detects Category as
a habit/checkbox column — and AddEntryModal checks isBooleanCol before
the categorical branch, so it rendered a toggle instead of the dropdown
the template's categoricalColumns configured. Pin habitColumns: [] on
the budget template, the same fix the "habits" template already uses
for its own name-collision.
2. No direct way to open an item's full detail view — only long-press →
context menu → "Open entry" — and tapping the item name to edit it
inline brought up the keyboard over a blank/black screen. The item
cell was wired through makeEditable (an inline <input>, matching plain
Table cells), not Tasks' click-to-open-expander pattern. Inline-editing
inside the newly min-width-forced, horizontally-scrolling item table is
almost certainly what triggered the black-screen repaint issue on iOS.
Replaced with a renderNameCell mirroring Tasks exactly: tapping the
name opens the note-expander modal (or creates/opens the linked page
with no notes column), a separate page icon is the only thing that
touches the filesystem. No more inline input on that cell at all.
New test locks in #2 (asserts no inline input on the item cell, click
fires openNoteExpander not the filesystem). 119 tests passing.
Note: an already-created budget file's fileConfig won't retroactively
pick up the habitColumns fix — only new files via "Create budget file".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
.csv-tasks-table (reused for Budget's item list) is table-layout: fixed +
width: 100% — with no min-width, a narrow viewport divides that 100%
evenly across columns regardless of content, and the wrapper's
overflow-x: auto never kicks in because the table never exceeds the
viewport. Tasks itself avoids this by computing an explicit min-width in
JS; Budget's table didn't. Confirmed via screenshot on mobile Obsidian —
Item/Date/Notes/Price headers and the row under them were overlapping.
Add the same per-column-type min-width budgeting Tasks uses (180px base,
matching .csv-tasks-name-cell's own min-width, +150 per generic column,
+110 for Price), forcing the table wider than the viewport so the wrapper
scrolls horizontally instead of squeezing cells unreadable.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
Adds "Create budget file" alongside the existing tasks/travel/habit/chart
templates — Date/Item/Category/Price/Notes, pinned to budget mode. Category
and Price already match the alias lists Budget/Kanban resolve by name; only
Item needs an explicit titleColumn override since "Item" isn't in
TITLE_COL_ALIASES.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tfyt1TBd7DiNic32s6E4nq
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
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
The "More screenshots" section was collapsed by default and stacked
the 3 extra images full-width, so seeing all four meant expanding a
<details> and scrolling. Now open by default with all four (including
the lead chart shot) in a 2x2 table layout, so they're visible together
without any interaction.
renderLibrary and renderTasks built their collapsible section headers
with `summary.innerHTML = \`...${genre}...\`` / `...${project}...` —
genre/project come straight from a CSV cell, so a value like
`<img src=x onerror=...>` would execute as markup instead of rendering
as text. Obsidian's plugin review flags raw innerHTML with dynamic
content for exactly this reason. Switched both to createSpan/text,
matching every other render call in the codebase. Layout is unaffected
(both headers are flex containers, so the plain text node between the
two spans was already an anonymous flex item — now it's a real span,
same visual result). Tests: 115 + 112 passed, typecheck clean.
README.md, docs/{architecture,css-classes,dev-workflow}.md, manifest.json,
package.json, package-lock.json, versions.json, .gitignore, LICENSE, and
tsconfig.json are re-added here with their current content only. Their
prior revision history is gone by design: an audit found 22 of README.md's
24 historical revisions still linked to handoff.md (a gitignored, never-
published file) — a dead reference that only ever disclosed the private
doc's existence, not its content, but was live across nearly the entire
project history until a recent cleanup. Since these are docs/metadata
rather than code, commit-by-commit revision history has no review value
here; only the current, already-audited content is kept. Code history
(everything else) is untouched.
Found by an actual read-through of every 1.1/1.2 file, not keyword search:
- src/utils.ts, src/view/chart.ts, esbuild.config.mjs: comments/code named
a specific timezone (CEST), keyboard layout (Swedish), and locale code
(sv-SE) used only for its date-format side effect — three independent
signals triangulating toward the same nationality. Genericized all three;
esbuild.config.mjs now formats the build timestamp manually instead of
depending on any locale string at all.
- main.ts, src/view/table.ts, src/view/toolbar.ts: "iPhone"/"iCloud"
framed as the author's own device/sync setup — genericized to "mobile"/
"sync".
- main.ts, src/inline-view.ts, src/travel-data.ts, src/travel-view.ts:
dangling "see handoff"/"HANDOFF" references to handoff.md, which is
gitignored and not part of the public repo. Replaced each with a
self-contained inline explanation instead of a pointer nobody can follow.
- docs/architecture.md: "Built for the author's book library and habit
tracking" softened to describe original use case without first-person
ownership framing.
- docs/dev-workflow.md: the project-tree diagram was stale (referenced
test-mobile-dashboards.mjs and xlsx-to-csv-roundtrip.mjs, both long gone)
and garbled (a cut-off local/ bullet with a "hardcoded-path caveat"
aside). Rewritten to match the actual current file list.
Tests: 115 passed, 0 failed. Typecheck clean. Build unaffected.
"Knowledge/..." was the author's actual legacy vault folder, used
throughout resolvePath and migrateFileConfigKey examples/tests and one
dev-workflow doc note. Replaced with a generic "Library/..." placeholder
everywhere it appeared. Tests unaffected (115 passed, 0 failed) since
these were just literal string fixtures, not behavior.
The native views are mobile-friendly, making the 📱 Mobile button a
redundant second rendering path with a Dataview dependency. The csv-add
and csv-refresh blocks its dashboards used stay fully supported for
back-compat; the generator + templates move to the local (gitignored)
tooling for anyone still stamping Dataview dashboards.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
handoff.md and the five live-vault scripts (dashboard regenerator +
simulator, normalizers, xlsx roundtrip verifier) move to a gitignored
local/ folder — they operate on the author's vault by name and are
useless to anyone else. test:all drops the vault-bound mobile suite
(template shape stays covered by the view smoke tests). Docs sanitized
to describe patterns, not the author's folder layout; the migration
script requires OBSIDIAN_VAULT with no default; remaining code comments
genericized.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No vault-layout defaults baked in; usage: node migrate-xlsx-to-csv.mjs
<folder> <basename...> with OBSIDIAN_VAULT for the vault path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The asset inherited from the travel-tracker visualizer had data-iso="" on
55 of its 177 paths: Australia, Brazil, Argentina, Iceland, Austria, and
50 others rendered but could never be colored confirmed/photo-only.
Regenerated from world-atlas countries-110m (Natural Earth, public
domain) via create-world-map.mjs — 176 countries, every path keyed, same
960×500 viewBox and integer quantization, 108 KB (was 113). Also: new
README screenshots — reworked tasks-board sample data and a travel-map
shot demonstrating the fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The only test dependency (travel_flat.csv) is now an inline template
string; create-sample-data.mjs imported the xlsx package that left with
the SheetJS retirement, so it couldn't even run. The repo ships code,
docs, and README screenshots — users bring their own data.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Data and image files live only at the tip — the history carries code and
docs; there was never anything but these exact synthetic fixtures, but a
data-free history is simpler to trust.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
README rewritten for the community store: pitch, install (BRAT + manual
with the world-map.svg caveat), quick start, view-mode and chart feature
tours, all five embed blocks, privacy/network disclosure (local-only
except opt-in AnkiConnect on localhost), development notes. MIT LICENSE
and versions.json added; manifest gains authorUrl and a current
description; settings tab heading finally says DataDeck instead of the
pre-rename XLSX Card View.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Merges task rows from a folder scan (task-shaped CSVs only; '/' = whole
vault) and/or an explicit file list into one board rendered by the same
renderTasks as the full view. Each file's columns map onto a canonical
Title/Type/Project/Priority/Due/Status/Notes set via name aliases
(buildAggregate — pure, unit-tested); files without a Project column
contribute their basename as the project. Edits write through to the
row's source file (done toggles, inline cells, the expander), guarded by
the sync-conflict stash, only touching files whose serialized text
actually changed; the board re-syncs off vault modify events. Context
menu gains 'Open source: <file>'. Replaces the old vault-wide DataviewJS
Main Dashboard pattern.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
doSave was a debounced whole-file overwrite from in-memory rows with no
check that the file changed underneath: with the same CSV open on Mac and
iPhone (iCloud), the second device's save silently ate the first's edits.
Both hosts now anchor on the last-read/written text; when the disk has
diverged at save time, the disk version is stashed to Archive/<name>
sync-conflict <stamp>.csv with a Notice, and the visible edits win the
write. CardView also gains the vault-modify re-sync the inline block
always had (stale full views + guaranteed conflicts before), skipping
mid-edit states so a pending save reconciles instead. Fixed alongside:
the inline host's external-modify handler never updated its sync anchor,
which would have false-flagged a conflict on every save after an
external change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Composites the (transparent) Chart.js canvas onto the theme background
before encoding, names the file '<csv> chart <date HHmm>.png', and writes
it via vault.createBinary. Works in both scatter/line and bar modes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Date-X charts gain two transforms: a Smooth toggle that draws a centered
7-day rolling-average line per series over faint raw dots (dots keep the
tooltips, the mean line carries the legend), and a By day/week/month
select that aggregates each series into calendar buckets (local-Monday
weeks) with a sum/avg/count pick — 'km per week, one line per person' in
two clicks. Mutually exclusive by design: a bucketed series is already
smooth. csv-chart blocks get bucket: and smooth: (true = 7 days, or a
number of days). Size-by hides under bucketing (no per-row point left).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Picking a categorical column as X (view picker or a csv-chart x: line)
flips into bar mode: one bar per value, with a count/sum/avg aggregate
select (persisted as chartAgg; agg: in blocks). Hue makes grouped bars;
comma-separated columns (Genre, Tags) split so a row counts once per
value; empty X buckets as a — bar pinned last. Fit/formula controls hide
in bar mode. Size-by maps a numeric column onto 3–14 px point radii with
a sqrt (area-true) scale — chartSizeCol / size: — with the raw value in
the tooltip. Bar controller registered in the shared lazy loader.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A Color picker in the Chart view (persisted per file as chartHueCol) and a
hue:/color: option in csv-chart blocks split the plot into one series per
distinct value of a categorical column, drawn from Obsidian's extended
color variables (Tableau-ish fallbacks). Best fit becomes per-series —
each dashed line keeps its series color, spans only its own x-extent, and
the footer reads 'A: +0.2/day (R² 0.91) · B: …'. Fit lines are filtered
out of the legend; tooltips gain a [series] prefix. Chart core refactored
from a single point-set to a series array (extractSeries, hueColumns).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Travel: headers were Country/Notes but analyzeTravel reads literal
lowercase keys — the scaffolded file opened in Travel view with every
row silently dropped. Now exactly travel.py's flat-CSV columns,
including city/visa_status/resolved (tourist detection + residency
exemptions read them).
- Tasks: added Type + Project (the columns the view's sections/grouping
are designed around — without Project, grouping fell back to Priority
headings), and Status is no longer typed Checkbox: the Add-form toggle
wrote 1/0 that the view's DONE_WORDS vocabulary didn't recognise.
DONE_WORDS also gains "1" so files from the old template still read.
- Habits: habitColumns pinned (auto-detect needs a row of values, so a
fresh file had zero toggles) and statusColumn explicitly disabled
("Read" is a status alias that polluted Stats/Library).
- New: Create chart file — a Date/Value/Notes measurement log pinned to
the Chart view with Value on Y and the fit line on.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every 'today' default came from toISOString().slice(0,10) — the UTC date,
which is the previous day between local midnight and UTC midnight in any
UTC+ timezone. Concretely: a habit logged at 00:30 CEST prefilled
yesterday's row, tasks flipped to overdue on the wrong midnight, archive
backups got yesterday's filename, and the new chart view's date ticks/
tooltips rendered local-midnight timestamps one day early. New
localISODate() helper in utils.ts used by all four call sites.
Also: a csv-add select whose saved value isn't a known option now shows
that value in the custom slot (the assignment was a dead 'val ? "" : ""'
ternary — the form displayed '—' while data existed, making it easy to
assume nothing was recorded).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Escape passed the live textarea value into closeInlineEditor — committing
the edit it was supposed to cancel — and the display:none that followed
blurred the textarea, firing a second commit. Escape now closes with the
original text, a closed-flag makes close idempotent, and an unchanged note
no longer queues a vault write on every open/close.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
makeEditable committed unconditionally on blur: clicking into a cell and
away again dirtied the file every time, and on date cells the write-back
was the 10-char truncated picker value — touching a date cell that carried
extra data silently clobbered it. Blur now compares against the value the
input was seeded with and skips the save on no-ops; Escape sets a cancelled
flag and blurs, so discard works even where element removal fires blur.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CardView.getDateCol ignored fileCfg.dateColumns entirely — a column typed
as Date in ⚙ Config only drove the dashboard/date sort if it happened to
sit in the first position, while the inline csv-view host already honoured
the config. The full view now checks the explicit pick first (but still
avoids the inline host's name-based fallback: 'Due' is date-named and that
would auto-default every tasks file to Dashboard).
Also gives InlineCardHost.getNotesCol the same '"" = disabled' semantics
the other role getters already have.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two bugs from the Config-modal functions-table redesign:
1. The modal stores "" for '— none —' and promises it disables the
function, and InlineCardHost honours that — but CardView's getters
(status/category/title/notes/image) used truthy checks, so "" fell
through to name detection and the function silently came back in the
full-page view. Getters now mirror the inline host: an explicit ""
means disabled, undefined means auto-detect.
2. The redesign dropped the one-function-per-column invariant the runtime
still assumes: assigning a role to a column now clears any other
explicit role on that same column, as the old per-column picker did.
Rewrote the six FileConfigModal smoke tests that still targeted the old
per-column Function select markup. Suite is green: 103 passed, 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The section sort pinned the literal bucket "Tasks", but the redesign keyed
buckets by raw type value ("task", "idea", …), so on real files the Tasks
section sorted alphabetically — landing last, after Idea/Note/Reference.
Task-like values (task/todo/action and empty, per TASK_WORDS) now fold into
one pinned Tasks bucket, matching the module's own documented intent; other
type sections render title-cased, A→Z. Updated the two smoke tests that
still asserted the pre-redesign Tasks/Notes/Ideas split.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Add datadeck-work symlink to the second vault's plugins folder and
update the deploy script to copy build output to both vaults.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Answers a question about Type=Checkbox semantics by tracing every
read/write path, and fixes the two inconsistencies found along the way.
Semantics (now consistent across all of them):
- Switching a column's Type to Checkbox never touches existing row data,
only fileCfg.habitColumns.
- "Checked" is a narrow truth-list, not a wide falsy-list: only
1/true/yes (case-insensitive) read as on. Everything else - 0, "",
no, false, or any leftover non-conforming text from before the column
was Checkbox-typed - reads as off, with no error. Extracted into a
shared isTruthyVal() in utils.ts (previously duplicated as
CardView.isTruthy and inlined again in the mobile add-form).
- Every write path normalizes to exactly "1" or "0", never blank.
Two real gaps fixed, both found by tracing the write paths:
1. NoteExpanderModal (the row-editor used by Kanban/Library/Tasks/Focus
card clicks) didn't know about Checkbox columns at all - editing an
existing row showed raw text you could type anything into, which
defeats the point of Type being a single source of truth. Added the
same toggle AddEntryModal already has, wired through a new optional
isBooleanCol param.
2. The mobile csv-add form had its own completely independent boolean
auto-detection from existing data, ignoring fileCfg.habitColumns
entirely. A column freshly retyped to Checkbox (before it had any
boolean-shaped data yet) rendered as a toggle in the Dashboard and
desktop Add modal, but stayed a plain text field on mobile. Now
checks the explicit list first, matching getBooleanColumns().
Tests: isTruthyVal's truth-table, and a new note-expander case
confirming a Checkbox column renders as a toggle (not text) and
normalizes a leftover non-0/1 value to "0"/"1" on click.
Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>
Two follow-ups from the Config redesign discussion:
1. AddEntryModal/NoteExpanderModal previously re-derived the title
column internally by name (Title/Name), ignoring FileConfig.titleColumn.
Both now take an optional titleCol param; main.ts and inline-view.ts
pass their already-resolved titleKey() (which honours the override)
instead of leaving each modal to guess independently. Same fix in the
mobile csv-add form (add-entry-form.ts), which already had fileCfg in
scope. A Title override configured in Config now actually excludes
the right column from every dropdown/select path, not just the ones
that happened to be named Title/Name.
2. getBooleanColumns() used a habitColumns.length > 0 check to decide
whether the explicit list wins over auto-detection - so explicitly
emptying the list (unchecking every Checkbox-typed column in Config,
meaning "no habit columns for this file") silently fell back to
auto-detect instead of actually turning the habit tracker off.
Changed to a plain truthy-array check, matching how categoricalColumns
already worked. This matters for the Habit-Dashboard question:
Type=Checkbox is already the single source of truth for which columns
are habit-trackable (no separate "special field" needed - a column
just has one Type, and Checkbox is what puts it in the grid) - this
fix makes that guarantee hold even at the empty-list edge case.
Tests: two new cases confirming an explicit titleCol override drives the
header/exclusion/delete-label in the expander and the dropdown exclusion
in the add form.
Co-authored-by: SVM0N <SVM0N@users.noreply.github.com>