Commit Graph

26 Commits

Author SHA1 Message Date
Aculix Technologies e6dd24a046 feat: installable offline app, plus export and import
Two things that were deliberately left out of 2.0.0.

Export writes every stored day to a JSON file. Import reads one back, and
only ever adds: a task whose id is already there is left alone, so
importing the same file twice does nothing and importing into a list
you're using can't lose work. That is also why it needs no confirmation
dialog. The trade is that import restores rather than reverts.

Both sit in a quiet line under the task list rather than the header, since
they get used about twice a year and the header is what you look at all
day.

For the PWA half, the service worker is about fifty lines with no
dependency, because the strategy falls out of how Vite builds. Documents
go network first and fall back to cache, so a deploy is picked up as soon
as you're online and nobody ends up stuck on an old build. Fingerprinted
assets go cache first and are kept, since their names change when their
contents do. No build-time asset manifest needed.

Icons are SVG in the manifest, which stays sharp at any size and costs
about a kilobyte, plus one 180px PNG because iOS wants a raster
apple-touch-icon. Two theme-color metas so the phone status bar follows
the theme, and safe-area padding on the header, without which the header
sits under the clock once installed on an iPhone.

Verified against the production build: worker registers and claims the
page, shell and assets land in cache, and with the server stopped the app
still loads, adds a task and persists it.
2026-08-16 02:16:31 +05:30
Aculix Technologies f27f1ee6d3 chore: ignore .claude
Local editor config, not something the repo needs. Also taken back out of
the earlier commit that added it.
v2.0.0
2026-08-16 01:53:19 +05:30
Aculix Technologies 6b01e4c246 docs: tighten wording, drop the planning scratch files
Removes docs/superpowers, which held a design note and an implementation
plan. Those were working notes rather than anything the project needs, and
they don't belong in the repo.

Everything else here is wording: em dashes swapped for ordinary
punctuation across the README and the code comments, and a few sentences
straightened out.
2026-08-16 01:50:59 +05:30
Aculix Technologies 7e030722a9 fix: build once on the host arch, not once per target
The v2.0.0 image failed on linux/arm/v7. Vite bundles with Rolldown, which
has no 32-bit ARM musl binary:

  Cannot find module '@rolldown/binding-linux-arm-musleabihf'

That came in with the Vite upgrade, but the older problem is that the
builder stage was rebuilt per platform under QEMU, when its output is
static files that are identical everywhere.

Pinning the builder to $BUILDPLATFORM runs install and build once,
natively, then copies the result into each nginx image. armv7 works again,
and two emulated npm installs go away with it.
2026-08-16 01:43:07 +05:30
Aculix Technologies 6e00c0a814 ci: publish a clean :latest tag, drop cosign signing
The package page showed a sha256 hash instead of a usable tag. cosign
pushes a separate artifact per build, tagged sha256-<digest>.sig, and
because it goes up after the image it was the newest entry. Sixty-odd
nightly runs had filled the listing with them.

Dropping the signing step leaves :latest visible. The tradeoff is real:
published images are no longer signed against the sigstore log. There's no
way to keep cosign and hide its artifacts, since GHCR renders them as
ordinary versions, so it's signing against a readable package page.

Also makes the tagging explicit instead of relying on the action default.
latest=auto moves :latest onto any pushed vX.Y.Z tag.

Version goes to 2.0.0: storage moved to a new key format, migrated on
first open, and Backspace no longer deletes.

Removes the analytics tag from index.html.
2026-08-16 01:39:29 +05:30
Aculix Technologies 19e8ac2f61 fix: fluid drop instead of the whole list lurching
Letting go of a card was abrupt and pulled its neighbours with it. Frame
capture showed non-dragged rows moving about 68px after release, still
going 13 frames later.

Two things owned transform on the same elements: manual shift transforms
for the gap, and animate:flip for the reorder. On drop, flip measured a
"before" rect that already had a manual offset in it, then the order
changed and the offsets cleared in the same update, so it animated toward
a position that never existed.

One owner each now. The list reorders as the pointer crosses each
boundary rather than on release, so flip moves the displaced cards on its
own, one swap at a time, and the swaps look right while you're dragging.
Only the lifted card is positioned by hand, with flip off for it so it
stays under the pointer. By release the order is already final, and
nothing moves but that card settling.

After the change, every non-dragged row moves 0px on release and the
dropped card eases 4px into place.

Slot geometry is replaced with offsetTop and offsetHeight, layout values
that ignore transforms, so the target can't feed back into itself and
can't go stale.

Removes src/lib/drag.js and its tests. The rule they covered doesn't exist
in this design any more.
2026-08-16 01:33:51 +05:30
Aculix Technologies 00d1e211fe feat: kanban-style drag feedback with a real drop gap
The old feedback was a card faded to 0.4 opacity and a 3px line above the
target. The fade read as "disabled" rather than "held", and a thin line is
a weak signal for where something lands.

The dragged card lifts now: full opacity, accent border, raised shadow, a
small scale and tilt. Cards it passes slide by the space it vacated, which
opens a real gap at the destination. The gap can't disagree with where the
card lands, because it is where the card lands.

Also fixes something found while building it. targetIndexFor measured live
rects, but those rows carry the shift transforms the drag applies, so the
midpoints used to pick the target moved as a result of picking it and the
choice oscillated. Slot geometry is captured once at drag start, relative
to the list so scrolling mid-drag is fine.

The displacement rule moves to src/lib/drag.js with tests. Its up/down
boundaries are where off-by-ones live.
2026-08-16 01:27:44 +05:30
Aculix Technologies 143fcc205d fix: undo no longer swallowed when focus is in the task input
Cmd/Ctrl+Z did nothing in normal use. The handler bailed out whenever the
event target was an input, which was meant to protect the field's own text
undo. But the add-task input is where focus usually sits: you click it to
add a task and stay there, and on macOS clicking a button doesn't move
focus. So the guard killed undo in the one situation it exists for, right
after deleting something.

It now defers to the field only when the field actually holds text. An
empty input has nothing to restore.

The decision moves to src/lib/shortcuts.js as a pure function, because the
inline version couldn't be tested. Shift+Cmd+Z means redo and no longer
triggers an undo.
2026-08-16 01:20:55 +05:30
Aculix Technologies b837ecd67f fix: load tasks before first render, not in onMount
Removing the splash exposed something it had been covering. Tasks loaded
in onMount, so every launch rendered one frame of the empty state first.

localStorage is synchronous, so there was never a reason to wait.
Migration and rollover run during initialisation and the first render has
the real list. onMount keeps only the rollover triggers.
2026-08-16 00:16:28 +05:30
Aculix Technologies 8d6780e379 docs: correct carry-over behaviour and shortcuts
The README described migration that never ran, and listed Backspace as a
delete key.

It now says what actually happens: unfinished work carries across gaps,
completed tasks are cleared with their day, storage prunes itself, and old
keys convert on first open. Adds Cmd/Ctrl+Z and Alt+Arrow, the touch long
press, the screenshot's new path, and how to run the tests.
2026-08-16 00:14:48 +05:30
Aculix Technologies 4c0b6de712 fix: pointer-events reordering with touch and keyboard support
HTML5 drag events never fire on touch, so reordering only ever worked with
a mouse.

Pointer events handle mouse, touch and pen in one path. The entry
condition has to differ: a mouse drag starts after 8px of movement, but a
touch drag can't use movement, because a vertical swipe on a list usually
means scroll. Touch starts on a 400ms long press instead, and moving
before that cancels it and lets the page scroll. touch-action applies only
while a drag is live, plus a non-passive touchmove blocker, since
touch-action can't stop a gesture already in flight.

Alt+Up and Alt+Down reorder from the keyboard. Focus is put back on the
moved row by hand, since list reconciliation drops it and the first
version only worked once.

Also removes dataTransfer.setData('text/html', event.target), which passed
a DOM node where a string was required.
2026-08-16 00:13:50 +05:30
Aculix Technologies cf64159322 fix: keyboard-visible delete, valid list semantics, undo support
The delete button was opacity:0 and only revealed on hover, so tabbing to
it showed nothing at all. The focus outline was being drawn on an
invisible element. It shows on :focus-within now too.

The row was role="button" with tabindex=0 while containing two real
buttons, which isn't valid. Rows are <li> in a <ul> now, so the list gets
announced with its count and position, and the checkbox carries the task
text plus aria-pressed.

Backspace no longer deletes. It's the key people hit meaning "go back",
and there was no way to get the task back afterwards. Delete still works,
and Cmd/Ctrl+Z reverses it.

Enter and Escape move from window to the input. On window, pressing Enter
with a task focused toggled that task and also added whatever was sitting
in the input.
2026-08-16 00:09:58 +05:30
Aculix Technologies 756b4254b4 perf: drop lottie, remove splash, fix theme flash
Bundle goes from 337 kB to 52 kB, or 89 kB to 20 kB gzipped. Almost all of
it was lottie-web, imported at the top level to draw one decorative empty
state, so every visitor paid 305 kB whether they ever saw it or not. It's
an inline SVG now, and it respects prefers-reduced-motion.

The 500ms splash is gone. There was no async work behind it.

Dark mode is applied by a small script in the head before first paint, so
dark-mode users stop getting a white flash on load. The theme class moves
to <html>, and App.svelte reads its initial state from that class instead
of re-reading storage after mount.

publicDir moves from assets/ to public/, which stops shipping the 200 kB
README screenshot to everyone.
2026-08-16 00:02:30 +05:30
Aculix Technologies 4857f1cc26 chore: upgrade to svelte 5 and vite 8
Svelte 4.2 to 5.56, Vite 5 to 8, vite-plugin-svelte 3 to 7. The old tree
wouldn't resolve incrementally, because a stale vite-plugin-svelte-inspector
pinned the v3 peer, so this is a clean reinstall.

Clears all 7 npm audit advisories, including a high-severity Rollup path
traversal.

Reactivity moves to runes: $state, $derived, $effect. Event directives
become properties, and main.js uses mount().

Also drops inputElement, a bind:this target nothing ever read.
2026-08-15 23:59:28 +05:30
Aculix Technologies 50509a9cc1 refactor: rewire App.svelte onto extracted lib modules
App.svelte is a view layer now. Storage, dates, rollover and task
operations come from src/lib, and selectedDate (a toDateString value)
becomes selectedKey (an ISO key).

Rollover runs on four triggers, because no single one covers everything:
mount, a self-rescheduling midnight timer, visibilitychange, and window
focus. The timer handles a pinned tab crossing midnight on its own. The
other two handle machine sleep, where timers don't reliably fire.

Persistence goes through one setTasks path so the stored list can't drift
from the visible one.

Checked in the browser against seeded legacy data across three days: keys
migrate and collapse correctly, unfinished tasks carry while completed
ones drop, and a simulated midnight updates an open tab with no reload.
2026-08-15 23:56:53 +05:30
Aculix Technologies f0fd6c1a65 feat: add pure task operations and bounded undo stack
Task operations become pure functions over arrays.

Ids move from Date.now() to crypto.randomUUID(), with a fallback for
non-secure contexts, since people do self-host this over plain HTTP on a
LAN.

Undo gets its own module so tasks.js stays pure. It covers single deletes
and clear-completed batches, and clamps indices because the list can
change between recording an entry and undoing it.
2026-08-15 23:52:33 +05:30
Aculix Technologies 5ce5b13ecd feat: add day-rollover with multi-day carry-forward
Replaces checkAndMigrateTasks, which never ran. Its guard compared
currentDate, set from new Date() at init, against a fresh new Date() a few
milliseconds later in onMount, so it was never true. Unfinished tasks have
been sitting under old date keys ever since.

The rule now: unfinished tasks from every prior day move into today,
oldest first, ahead of whatever is already there. Completed ones are
dropped and the old key deleted. It spans gaps, which the original never
did even in theory, since it only looked one day back.

now is a parameter. That's the part that makes the boundary testable.
2026-08-15 23:51:36 +05:30
Aculix Technologies d69c68ccef feat: add resilient storage module with legacy key migration
A factory over an injectable backend, so persistence can be tested without
a DOM.

Fixes the unguarded JSON.parse that would white-screen the app on one bad
value, and falls back to memory when localStorage itself throws (Safari
private mode, full quota).

migrateLegacyKeys converts old "Sat Aug 15 2026" keys in place. It's
idempotent, and it leaves anything it can't parse alone rather than
deleting it.
2026-08-15 23:50:20 +05:30
Aculix Technologies e81e7ef8e7 feat: add date-key module with local-time formatting
Switches task keys to ISO YYYY-MM-DD. They sort, so finding every day
before today is a string compare instead of re-parsing each key.

Two things worth knowing. fromKey builds from date parts rather than
parsing a bare ISO string, which gets read as UTC and lands on the wrong
day west of Greenwich. addDays goes through setDate, so it stays on the
same calendar day across a DST change instead of adding a flat 24 hours.
2026-08-15 23:49:29 +05:30
Aculix Technologies d4ddceaf73 chore: add vitest, commit the lockfile, fix gitignore and docker install
Groundwork before pulling logic out of App.svelte.

Vitest runs in a node environment. Nothing under test needs a DOM, because
every module takes its dependencies as arguments.

package-lock.json is tracked now and the Dockerfile installs with npm ci,
so the nightly rebuild can't quietly land on different versions.

.gitignore: .DS_Store was anchored to the root and missed nested copies,
and dist/ wasn't ignored at all.
2026-08-15 23:48:55 +05:30
Aculix Technologies 056f9fc562 Refactor date display logic in App.svelte to use reactive variable for improved readability 2025-10-16 17:32:02 +05:30
Aculix Technologies 2697a81f72 Enhance README with app description and screenshot 2025-10-16 14:49:20 +05:30
Aculix Technologies cfff2eec42 Update README 2025-10-16 14:35:11 +05:30
Aculix Technologies 345f9ee347 Update README 2025-10-16 14:29:21 +05:30
Aculix Technologies LLP 5fe1669b35 Create docker-publish.yml 1.0.0 2025-10-16 14:18:46 +05:30
Aculix Technologies 876ba3c835 first commit 2025-10-16 14:16:01 +05:30