Repository navigation
Releases
v1.9.2
Security
- Closed a CodeQL
js/xss-through-domfinding (alert #2) in the epic colour picker —renderTagRow()'s quick colour picker (src/js/02-utils.js) read the raw<input type="color">value and wrote it straight intocat.color(persisted state) and the swatch'sstyle.background, bypassinggetCat()'s existingsafeCssColor()sanitisation entirely; theinnerHTMLsink for the picker's ownvalueattribute relied on that same upstream sanitisation, which CodeQL's dataflow analysis doesn't credit as a barrier across the object spread. All three sites now callsafeCssColor()explicitly. A real<input type="color">clamps its.valueto valid hex at the DOM level, so this isn't exploitable through normal use today — it closes the gap CodeQL flags and guards against any future code path (e.g. a non-native picker, or a refactor) that could feed an unclamped value through the same handlers. 3 new regression tests intest/unit.mjs(VM-sandboxed, since a real browser can't hold an invalid value in a colour input to prove the "before" case). This alert had been open since PR #87 (2026-05); a prior attempt (PR #265) was closed as superseded without merging.
Removed
- Dead V5 timer-bar CSS in
_timer.scss— the.timer-barlayout (circle ticker, task-name block, action pills, note row, utility pills, paused-state variants, and the.timer-task/.timer-elapsedcompat stubs left over from an even earlier layout) was fully superseded by the hero card redesign and had zero references left in any.jsor.htmlfile — confirmed with a full cross-check, not just the.tb-*prefix grep the backlog note was based on, which also caught.timer-pulse,.timer-paused-lbl,.timer-pause/.timer-stop, and.timer-distractas dead too. Kept.tb-mood-panel/.tb-mood-item(still used by the hero card's own mood picker, interleaved in the middle of the dead block) and@keyframes pulse(still used by.entry.is-timing .edot's animation)._timer.scssgoes from 691 to 344 lines; compiledstyles.cssshrinks by ~4KB gzipped. No behaviour change — verified visually in the browser (mood panel opens with the right styling,.edot's pulse animation still fires) plus the full build/lint/test suite.
Fixed
- Log view entries sort by start time, not insertion order —
viewEntries()(src/js/02-utils.js) produced its newest-first ordering by reversing insertion order, which only matches chronological order when entries are logged in the order they happened. An entry added retroactively — filling in a missed morning slot after the rest of the day was already recorded — therefore landed wherever it happened to be typed rather than at the position its start time called for. It now sorts bytsdescending, so an entry always appears where its start time says it belongs regardless of when it was added. Knock-on effect:exportTxt()derives its oldest-first ordering by re-reversingviewEntries(), so the exported timesheet is now genuinely chronological too, rather than reversed-insertion-order. 2 new regression tests cover thetsordering independent of insertion sequence, and the existing viewed-date filter. - Gap check (end-of-week report and end-of-day export warnings) no longer flags non-billable entries for a missing note or proof link —
findGapReportEntries()andfindExportWarnings()(pure-fns-export.js) previously required every finished, non-cancelled entry to carry a note or link regardless of billing status, so meetings, admin work, and other entries marked internal via the 💰/💸 toggle showed up in the weekly gap report and the export's warnings section even though there's nothing to bill and so nothing to prove. Both functions now skip an entry whose resolved_billablestatus isfalse; callers (exportTxt()in05a-export.js,openGapReportOverlay()in12c-gapreport.js) resolve that status via the existingisEntryBillable()three-tier lookup before calling in, since the pure module has no access to the category/task state that lookup needs. smoke-tests.cjscouldn't run in sandboxes with a pre-provisioned Chromium that lags Playwright's pinned version —chromium.launch()always resolved the browser Playwright's own installer would have downloaded for the currently pinnedplaywrightversion, with no way to point it at a different binary already on disk. In an offline/sandboxed dev environment where a Chromium build exists at an older revision thannode_modules/playwright-core/browsers.jsonexpects, andnpx playwright installcan't reach the network to fetch a matching one, this failed withbrowserType.launch: Executable doesn't exist at .../chrome-headless-shell. Real CI is unaffected —ci.yml,auto-fix-ci.yml, andauto-chore.ymlall runnpx playwright install --with-deps chromiumbefore tests, which keeps the download in sync withpackage.json.chromium.launch()now accepts an optionalPLAYWRIGHT_CHROMIUM_EXECUTABLE_PATHenv var; unset (the default everywhere else), behaviour is unchanged.auto-chore.yml's exit-code checks could misfire on an empty string — the "comment on the issue" step'selif [ "${{ steps.claude.outputs.exit_code }}" != "0" ]and the "fail the job" step'sif: steps.claude.outputs.exit_code != '0'both treat an empty/unsetexit_codeoutput as "nonzero," since'' != '0'is true. In the current workflow theclaudestep always sets this output before finishing (wrapped inset +e/set -e), so the gap wasn't reachable in practice — but a hard runner kill mid-step (cancellation, timeout) before that finalechoruns would have produced a spurious 🔴 "CLI crashed" comment and an unwarranted red job, misreporting what actually happened. Both checks now require the output to be non-empty first. Flagged as a nitpick on PR #296's own review and tracked as #297; picked up separately since it editsauto-chore.ymlitself, which the automation can never push (see the workflow's own header comment).auto-chore.ymlfailed silently when a push failed, and briefly shipped with an invalidpermissions:entry — found by dogfooding the workflow on a real test issue (#297) asking it to modifyauto-chore.ymlitself: the defaultGITHUB_TOKENrefused the push ("refusing to allow a GitHub App to create or update workflow ... withoutworkflowspermission"), and because that push step failed, the "comment on the issue with the outcome" step was skipped too (GitHub Actions skips subsequent steps by default after a failure) — leaving the issue with zero visibility into what happened. The comment step now runs regardless of an earlier step's outcome (!cancelled()), with a distinct message for "committed but failed to push" versus the existing "declined to automate" and "CLI itself crashed" cases. A same-day follow-up (#299) first tried addingworkflows: writeto the job'spermissions:block —workflowsis not a real GitHub Actions token permission scope, and the invalid key made the entire workflow file unparseable, breaking every chore trigger, not just ones touching workflow files. Corrected here: there is nopermissions:entry that grants this — pushing changes to.github/workflows/*is a hard limit of the defaultGITHUB_TOKEN, now documented as a known limitation in the workflow file's header comment. The distinct failure comment above is what actually surfaces this case.auto-chore.ymlno longer silently swallows a Claude CLI crash —> chore-output.txt 2>&1 || truediscarded the CLI's real exit code, so a crash or auth failure looked identical to Claude correctly declining to act: same green run, same generic issue comment. The step now captures the actual exit code; a nonzero exit posts a distinct 🔴 comment and fails the job outright, while a zero exit with no commit still reports the original "declined to automate" message. Also added aconcurrency: auto-chore-<issue-number>group so a fast label-remove-then-reapply can't race two overlapping runs past the existing-branch/PR guard into duplicate PRs, and a${SLUG:-untitled}fallback for issue titles with no ASCII alphanumeric characters (previously produced a branch name with a trailing dash). Found via/pr-reviewon #291 after it had already merged.
Added
- CI now lints the workflow files themselves (
actionlint) — an invalid key in a workflow does not fail loudly: GitHub refuses to parse the file, schedules zero jobs, and reports no error anywhere, so the run reads as "nothing to do". That has silently broken this repo twice — PR #256 (if: secrets.CLAUDE_REVIEWER_APP_ID != '', a context unavailable in step-levelif:, which lefta11y-audit.ymldead for weeks without ever posting a check) and PR #299 (workflows: writein apermissions:block, which killed every chore trigger until #300). Both were found only by checkinggh api .../jobsby hand and noticing an empty list. Thelintjob now runs actionlint, pinned to v1.7.12 and SHA256-verified before it executes. Because a linter that silently stops enforcing is worse than none,npm run test:actionlintalso re-runs actionlint against two fixtures reproducing those exact bugs and fails if either rule stops firing — matching on actionlint's message text rather than its rule tag, since tags have been renamed upstream before. PureparseActionlintFindings(),interpretFixtureRun(),interpretWorkflowsRun()andinterpretSelfTest()in.github/scripts/lib/actionlint-selftest.mjs, 20 unit tests that need no binary installed. Linting ofrun:block contents (shellcheck, pyflakes) is deliberately left off for now — that is a separate, much larger cleanup. Twoactions/create-github-app-tokeninput findings are also muted: actionlint v1.7.12 ships a stale schema for that action and reportsclient-idas undefined andapp-idas required, when the action's ownaction.ymldeprecatesapp-idin favour ofclient-id— acti...
v1.9.1
Changed
- Non-billable relabeled as "internal" — the billable/non-billable boolean already covered the distinction that mattered (client-billable vs. not), so rather than add a redundant third field, "non-billable" is now labelled "internal" everywhere it appears: the entry-row toggle, the task-row toggle, the category-manager button, and the export summary line. The underlying
billableboolean and its entry → task → category inheritance (isEntryBillable()in05-entries.js) are unchanged.
Added
- Weekly report draft — a new "📝 weekly report" button in the export row opens a copy-to-clipboard panel summarising this calendar week (Mon–Sun) grouped by Jira ticket key, with tracked time and any notes/links per ticket, so the "what did I touch" reconstruction needed for a status report no longer means cross-referencing three systems by hand. Untracked/non-ticketed work is grouped into its own "No ticket" bucket rather than dropped; break/lunch/meeting entries are excluded, matching the gap report. This is the feature the proof-link/note fields (PR #276) were captured for. New pure
buildWeeklyTicketSummary()andformatWeeklyTicketSummaryText()inpure-fns-export.js, both unit tested; new12d-weeklyreport.js. - Weekly plan review checklist — planned-ahead ("upcoming") tasks sometimes turned out to already be finished elsewhere by the time their week actually arrived, since nothing ever re-checked them beforehand. A dismissible banner now appears once a new ISO week begins if any "upcoming" task is dated within it, opening a checklist (ticket key via the existing
parseJiraLabel(), date, and "✓ done" / "✕ drop" actions per row) to reconcile the plan against reality. This app has no live Jira connection, so the checklist only ever prompts — it can't auto-detect completion. New purefindWeeklyPlanReviewTasks()inpure-fns-tasks.js; new file10e-weeklyplan-review.js. - End-of-day export reminder — exports were always taken mid-afternoon, so the last day's JSON backup was always incomplete. A dismissible banner now appears in the export section once today's workday looks like it may be over (day-start + 8 hours, using the existing start-of-day timestamp — adapts to whenever the day actually began rather than assuming a fixed clock time), with an "🌙 end the day" action that triggers the real export flow. This is a reminder, not a silent auto-export — the app still never writes a file without an explicit click. Pure, unit-tested
isWorkdayLikelyOver()inpure-fns-export.js; re-checked every 5 minutes so it can appear while idle. - End-of-week gap report — a new "🔍 gap report" button in the export row lists this calendar week's (Mon–Sun) finished, non-cancelled entries that have neither a proof link nor a note, so gaps in documentation surface before a weekly report is due instead of after. Clicking a flagged entry ("+ fix") jumps straight to it in the Log view with its proof-link/note editor already open. Break/lunch/meeting utility entries are excluded, since they never need documentation. New pure
findGapReportEntries()inpure-fns-export.jsandmondayOfWeek()inpure-fns-format.js(the latter also replaces a duplicated inline calculation in thestatWeekheader stat); both unit tested. - Timer long-running safety warning — the two longest-ever log entries (12h and 7h) both ended exactly when a JSON backup was taken, a strong signal of a timer left running unattended. The Hero Card now shows a dismissible amber warning once a running timer passes 4 hours (
LONG_RUNNING_MINSin03-timer.js), with a "stop timer" action; the threshold check is a pure, unit-testedisLongRunningTimer()inpure-fns-format.js. Separately, returning to the tab after it was hidden for more than 30 minutes while a timer was running now asks (viaconfirm()) whether to stop it — this only ever asks, it never changes timer data on its own. Both checks reset per timer session (startTimer()). - Proof link and note fields on log entries — each time-log entry can now carry a
link(the concrete artefact the work touched: Confluence page id, Zephyr case/cycle key, filename, or a URL) and anote(a one-line "what I did," written at the time). Both are optional and edited via a new 📎 button on the entry row, which opens an inline panel with a link input and a note textarea — populated fields show as 🔗/📝 indicators instead, and a link starting withhttp(s)://renders as a clickable anchor. Entry notes are exported to the.txttimesheet alongside any plan-task note for the same task (newbuildEntryNoteMap()andmergeNoteMaps()inpure-fns-export.js); the proof link is captured for now and will feed the planned JSON report-draft generator. New unit tests forvalidEntry's optionallink/notetype-checking and both new pure functions. - Task notes exported to the timesheet —
exportTxt()now appends each task's plan note as an indentednote:line under that task's entry in the.txtexport, so context you jot on an in-progress task (via the existing 📝 note button) travels with the day's timesheet instead of staying trapped in the board. Matching is by date + case-insensitive task text (the same convention the app already uses to link a tracked entry back to its plan row — there is no sharedtaskId). New purebuildTaskNoteMap()inpure-fns-export.js, wired intoformatGroupedLines()'s existingtaskNotesparameter; 9 new unit tests.
Fixed
npm ci+ commit broken on any fresh clone or worktree —@commitlint/config-conventional@21.2.0pulls inconventional-changelog-conventionalcommits@10.x, whosepackage.jsononly exposes an ESM-only"exports"field (import/types, norequire). That broke@commitlint/resolve-extends's CJSrequire.resolve()call withERR_PACKAGE_PATH_NOT_EXPORTED, failing thecommit-msghook on every fresh install. It went unnoticed because the main working directory's stalenode_modulesstill had the older, CJS-compatible9.3.1installed. Pinned back via apackage.jsonoverridesentry —parser.js(the file commitlint actually consumes) is byte-identical between the two versions, so the pin is behaviourally safe.- Log entries couldn't create a new epic — the category picker on the task board has always had a "+ new epic" control, but the equivalent picker on log entries (opened via the
● category ▾button under a Log-view entry) never got one. In practice this meant epics could only be created from a task card, so tasks logged straight from the timer/adhoc-log input — as opposed to Jira-imported tasks, whose epics were already set up on the board — had no way to be tagged under a new epic.buildEntryCatPickerHtml()extracted into04-render.js(matchingcatOptsin10a-tasks-row.js) and given the same inline "+ new epic" creator, wired up with matching event handlers. The board's and the entry's "+ new epic" handlers previously duplicated the same create/dedupe/colour-pick logic; both now call a sharedcreateCategory()in01-state.js. 9 new unit tests. - Ad-hoc "+ log" row did nothing on a zero-entry day —
render()'s empty-state branch rendered the inline quick-add row into#timelinebut returned before its click/Enter bindings ran, so logging something on a fresh day (or any day with nothing logged yet) silently did nothing. Binding logic extracted intobindAdHocRow()and called from both branches; 4 new regression tests intest/unit.mjs. - JSON backups no longer grow without bound — the 21-day retention window was only applied to
entries, whileplanTasks,blocks,devLog, anddistractionswere bundled in full. Those arrays grow every day (carried tasks, per-day time blocks, appended distraction/dev-log records), so thework-log-backup-21d-*.jsonfile kept getting bigger despite its name. All five time-series arrays are now trimmed to the same window;categories,qpHidden, and the source-cappedpomoLogare still kept whole. Retention now runs through a pure, unit-testedbuildBackupPayload()inpure-fns.js(7 new tests), and the per-array count of excluded records is logged at export time. - Tabbed task board fills the full panel width — the To Do / In Progress / Done tab bar and the active lane (with its cards) were collapsing to content width and left-aligning, leaving the right side of the panel empty. The tabbed flex-column layout was inheriting
align-items: startfrom the base.board-colsgrid; it now resets toalign-items: stretchso tabs and lane span the whole width. Regression test added insmoke-tests.cjs. - Manually-added tasks never showed as "In progress" on the Kanban board — the board's own "▸ track" button correctly promoted a task's status when starting its timer, but the three other ways to start tracking — the hero "WHAT'S NEXT?" composer, its recent-task chips, and the quick-capture overlay — only created a log entry and never touched the matching
planTasksrow, so the card stayed in To Do no matter how long the timer ran. Jira-imported tasks weren't spared either, but were rarely affected in practice since importing already seeds their status from Jira and users tend to interact with them via the board's own controls. New sharedfindPromotableTask()(pure, inpure-fns-tasks.js) andpromoteMatchingTaskToInProgress()(10-tasks.js) wrapper, now called from all four start-tracking entry points —addEntry(),_heroHandleStart(),_heroStartFromChip(),_qcActivateRow(), and the board's own handler (deduplicating its previous inline copy of the same logic). New unit tests cover the lookup, the mutation (status flip,completedAtclearing, parent-task promotion), and that every entry point actually calls through.
Config defaults at this release
These are the compile-time defaults baked into src/js/00-config.js.
Runtime overrides (weather location etc.) are set per-machine in `config.lo...
v1.9.0
Config defaults at this release
These are the compile-time defaults baked into src/js/00-config.js.
Runtime overrides (weather location etc.) are set per-machine in config.local.ps1.
| Constant | Default value |
|---|---|
WEATHER_LAT |
60.1887 |
WEATHER_LON |
24.927 |
WEATHER_NAME |
'Helsinki' |
JIRA_BASE |
'https://your-instance.atlassian.net/browse' |
DAILY_GOAL_MS |
7.5 * 60 * 60 * 1000; // 7h 30m |
AUTO_PAUSE_ON_TAB_SWITCH |
false |
v1.8.9
Config defaults at this release
These are the compile-time defaults baked into src/js/00-config.js.
Runtime overrides (weather location etc.) are set per-machine in config.local.ps1.
| Constant | Default value |
|---|---|
WEATHER_LAT |
60.1887 |
WEATHER_LON |
24.927 |
WEATHER_NAME |
'Helsinki' |
JIRA_BASE |
'https://your-instance.atlassian.net/browse' |
v1.8.4
Config defaults at this release
These are the compile-time defaults baked into src/js/00-config.js.
Runtime overrides (weather location etc.) are set per-machine in config.local.ps1.
| Constant | Default value |
|---|---|
WEATHER_LAT |
60.1887 |
WEATHER_LON |
24.927 |
WEATHER_NAME |
'Helsinki' |
JIRA_BASE |
'https://your-instance.atlassian.net/browse' |
v1.8.0 — Modular architecture, full JSDoc, a11y & tooling
v1.8.0 — Modular architecture, full JSDoc, a11y & tooling
This release marks the transition from a single-file architecture to 18 focused JS modules built at compile time — the same single work-log.html to open in your browser, but now with a proper source structure, full documentation, and a real toolchain.
Code Quality & Documentation
- Full JSDoc coverage across all 18 JS source modules — every public function has
@param,@returns, and@throwsannotations - CITATION.cff — software citation metadata for academic/open-source use
- DATA.md — complete localStorage schema reference (every key, field type, lifetime, and data-recovery recipe)
- ARCHITECTURE.md — module dependency map, data flow, and integration overview
- Key assumptions documented inline: billing rounding midpoint, billable defaults, carry-over rules
Accessibility
- ARIA labels on all icon-only buttons
role="dialog"/aria-modalon all modals with focus trap (focus saved on open, restored on close; Escape closes)role="region"on stat and pomodoro sections;role="list"on task listsaria-liveon dynamic counters;aria-expandedon collapsible headers (keyboard: Enter/Space)
Automation & Tooling
- Husky pre-commit hook running ESLint + Prettier via lint-staged
- Dependabot auto-update for npm dependencies
- GitHub Releases workflow — auto-publishes release notes from CHANGELOG on tag push
- GitHub Pages workflow — generates and publishes JSDoc reference on every push to
main - Lighthouse CI workflow — audits a11y/best-practices/performance with thresholds
- Branch ruleset — force-push and deletion blocked;
test+lintrequired to merge
Configuration
- Hardcoded config extracted to
src/js/00-config.js: location coordinates, Jira base URL, calendar account labels — one file to edit when adapting for a different environment
Bug Fixes
- Three-state checkpoint toggle (
false → 'partial' → true) fixed in focus-mode overlay - Smoke test suite updated and expanded to 199 tests, 0 failures
Upgrading from v1.7.x
No localStorage schema changes — your existing data is fully compatible.
The built script.js and styles.css in the repo root are ready to use.
To build from source or run tests:
npm install
node build.js # rebuilds script.js + styles.css
node smoke-tests.cjs # 199 testsv1.7.0
v1.7.0 — May 20, 2026
Sunday, May 18
- Calendar: read from all Outlook accounts (GetDefaultFolder + folder tree walk)
- Calendar: TODAY'S MEETINGS section header, collapsible, matches plan-section style
- Calendar: past meetings grey/italic, ongoing meeting pulses blue, stops when meeting ends
- Calendar: account label per meeting ([Gofore]/[LähiTapiola])
- Calendar: ▶ start button adds meeting to today's plan and starts timer
- Calendar: ✕ delete button hides meeting for the day (persists in localStorage)
- Calendar section moved above today's tasks
- Nameday: Swedish flag SVG replaces emoji (reliable on all Windows fonts)
- Flag days: added Kaatuneitten muistopäivä, Eino Leino, Miina Sillanpää, Swedish Heritage Day, Children's Rights Day, Finnish Nature Day
- Nameday + calendar API proxied through local server to bypass CORS
- CSP: allow any localhost port and 127.0.0.1 for server flexibility
- Timesheets folder removed from git tracking, added to .gitignore
- Fix carry-over: pending/blocked status propagates to today's copy
- Re-implement upcoming status and 🔜 Upcoming Tasks section
- Fix: timer input fields now have bright white text on dark background
- EOD modal: notes-for-tomorrow section with per-task handoff inputs; note shown inline next morning
Monday, May 19
- Task checkpoints: inline step list with progress bar, tick-off, delete, drag-to-reorder; carried forward on day rollover
- Focus mode: checkpoints shown for active task, Pomodoro fixed below exit button, meetings visible
- Checkpoint steps: double-click label to edit inline; Enter/blur saves, Escape cancels
- Fix: checkpoint badge color mirrors task status (amber/purple/red) until progress begins
- Fix: checkpoint badge status color only applied when checkpoints exist
- Completed tasks expire at iteration boundaries instead of 14-day rolling window
- Iteration expiry dates moved to localStorage; editable via 📅 iterations button
- Calendar meetings sorted by start time
- EOD handoff notes: only show tasks actually worked on today
- Moved parked thoughts section between today's meetings and today's tasks
- Smoke tests: fix streak assertion, fix cal-delete-btn, add tests 32–34 (checkpoints, handoff notes, parked thoughts)
Wednesday, May 20
- Billable/non-billable: 💰/💸 toggle on all task rows + category default; retroactive update on category change
- Billable emoji shown in time log entries; clickable to toggle matching plan task
- Export: billable/non-billable totals in header; pasteable summary line grouped by category
- Quick-pick: tasks past iteration boundary hidden automatically
- Timeline + export: consecutive same-task blocks with <30min gap merged into single block
v1.6.0 — 15 May 2026
Tasks
- Upcoming status — 🔜 moves tasks to a dedicated Upcoming Tasks section; excluded from overnight auto-carry
- Pending & Blocked statuses — tinted section with comment history and timestamps
- Subtasks — ⊕ split button; child tasks grouped under parent; auto-promote/complete parent
- IDKW button — 🎲 picks a random To Do task
- Auto-stop timer on done — marking Done stops the timer and closes the log entry
Calendar & Meetings
- M365 calendar strip — today's meetings with time, duration, Teams Join link (local Outlook COM)
- Past / Upcoming split — meetings grouped into sub-sections, re-evaluated every 60 seconds
- Finnish calendar events — flag days, holidays, notable days from the official Finnish calendar API
- Nameday — official Nimipäivärajapinta API; Finnish plain, Swedish dimmed with sv: label
- Flag day display — SVG Finnish flag, "Next flag day:" prefix, full month name
Header & Stats
- Streak fix — sub-stat starts from yesterday, consistent with main streak counter
- statToday — counts unique task names
Timer
- Parked thoughts — 💭 captures a thought mid-flow; → task or ✓ dismiss
- safeRoundedStart() — prevents new entries overlapping the previous entry's tsEnd
- Pomodoro in focus mode — ring visible during Emergency mode
Timeblock
- Extended hours — grid expanded to 07:00–21:00
- Emoji picker — ✦ button on each slot
- Simplified editing — all blocks edited directly on the grid
Jira Integration
- CSV importer — drag-and-drop Jira export; category mapping, select/deselect, duplicate detection
- Clickable ticket links — AITO-XXXXX prefixes become hyperlinks
Quick Pick
- Hide / restore tasks — × removes from recent list; persists in localStorage
Timelog
- Inline editing — click any entry text to edit in place
- Timelog header and wider chart labels (200 px)
Exports
- Workday length — .txt exports include day started, ended, total duration
- File System Access API — saves directly to timesheets/ and backups/ folders
Smoke Tests
- 114 → 136 assertions; tests 25 (Upcoming lifecycle) and 26 (timer auto-stop) added
v1.5.0
v1.5.0
Jira CSV importer
- New collapsible "Import from Jira" section in the plan panel
- Drag and drop a Jira CSV export to load issues, or click to browse
- Category mapping panel shows how Jira components map to your epics
- Select/deselect individual tasks before importing; duplicate detection skips tasks already in today's plan
Quick pick improvements
- Remove button on quick pick pills (hover to reveal, click ✕ to hide)
- "Restore" link to bring back hidden items; persists across reloads
Inline entry editing
- Click any entry text in the time log to edit it in place
- Press Enter to save, Escape to cancel
Plan section polish
- Section headers restyled with icon prefixes
- Pending/Blocked tasks get a dedicated tinted section with comment history
- Done and completed task colours improved for light and dark mode
Timeblock
- Emoji picker added to timeblock slots
- Meeting form removed (simplified)
Time log
- Timelog header added
- Chart label column widened for longer epic names
- Day start/end times tracked in exports
v1.4.0 — Emergency mode, handoff notes, emoji, start of day, timeblock fixes
New features
🚨 Emergency / focus mode — strips UI to current task, smallest next action, and timer. Press 🚨 focus in timer bar, Escape to exit. Next action persists per task.
✍️ Transition handoff note — first Stop click shows a context field. Saves per task name, shown next time you start that task.
🌅 Start the day button — sets work start time; timeblock only shows untracked slots from that point. Sits below the ← Today → nav.
🎨 Category colour picker — click the coloured dot while renaming a category to open a native colour picker. Saves with the name.
😀 Task emoji — emoji button on each plan task opens a compact picker (45 common emojis + free text). Shown before task name in plan and completed sections.
📅 Meeting category — now light blue (#7EC8E3) with 📅 prefix in plan list.
🔔 Chime interval control — dropdown in Pomodoro: off / 15 / 30 / 45 / 60 min. Persists across reloads.
Bug fixes
- Timer not ticking —
liveEntryundefined inrenderTimeblockcrashed render on every log entry beforesetIntervalcould be reached -
- Day switch without restart — midnight rollover now detected in
tickClockevery 10s; auto-carries tasks and re-renders
- Day switch without restart — midnight rollover now detected in
-
- Export banner null crash —
checkNewDay/checkSnapshotreferenced removed HTML; replaced with no-ops
- Export banner null crash —
-
_lastTickDateTDZ — declaration moved before first use in startup sequence
-
- Task in both In Progress and Done — marking done now retires all older same-text versions; completed section deduplicates
-
- Untracked slot boundary —
tsEndexactly on 30-min boundary mapped to next slot viaMath.roundintimeToSlot; fixed by subtracting 1 from slot index on boundary
- Untracked slot boundary —
-
- Paused timer covers wrong slots — live block and covered-slot calc now cap at pause point (
entry.ts + accumulatedMs), notDate.now()
- Paused timer covers wrong slots — live block and covered-slot calc now cap at pause point (
-
- Parent not promoted —
promoteParents()runs at top of everyrenderPlan()call as an enforced invariant
- Parent not promoted —
-
- Handoff text invisible — text colour hardcoded to
rgba(255,255,255,0.9)for dark timer bar
- Handoff text invisible — text colour hardcoded to
-
- Canvas null crash in
setFavicon—getContext('2d')null-checked and wrapped in try/catch
- Canvas null crash in
-
setIntervaldecoupled fromtickTimer— interval set up before first tick in all start paths
Tests
70 → 114 smoke tests passing. New areas: emergency mode, handoff, day-change fixes, task retirement, untracked boundary, paused block cap, parent invariant, task emoji, start of day.
Security
- Task emoji button rendered
t.emojiunescaped in innerHTML — fixed withescHtml()