Members
(constant) AUTO_PAUSE_ON_TAB_SWITCH :boolean
When true, pauses a running timer automatically when the browser tab becomes hidden (visibilitychange → document.hidden). The pause is silent; the event is recorded in the console log via wlLog.info but no UI notification appears. Set to false to disable. Override in 00-config.local.js.
Type:
- boolean
- Default Value:
- false
- Source:
(constant) BACKUP_RETENTION_DAYS
How many days of entries to include in each backup file.
- Source:
(constant) CAL_ACCOUNT_LABELS :Object.<string, string>
Maps raw Outlook account keys (lowercase) to human-readable labels shown in the calendar strip. The PowerShell server sends the DisplayName field; this map handles display-name variants, email domains, and substring matches.
Add an entry for each calendar account you want labelled; unknown accounts are shown without a label badge.
Type:
- Object.<string, string>
- Source:
(constant) COLLAPSE_PREFIX
localStorage key prefix for section open/collapsed state.
- Source:
(constant) CUSTOM_PALETTE
Fallback colour palette nextDistinctColor() (01-state.js) draws from when
auto-generating a new category's colour, in preference order. Every value
here is a plain hex string and does not itself need sanitising — but any
user-supplied colour (e.g. from the category colour picker) must still
pass through safeCssColor() before use, same as everywhere else in the app.
- Source:
(constant) DATE_LABEL_DAYS
Day-of-week abbreviations used by fmtDateLabel — module-level to avoid per-call allocation.
- Source:
(constant) DATE_LABEL_MONTHS
Month abbreviations used by fmtDateLabel — module-level to avoid per-call allocation.
- Source:
(constant) DEFAULT_CATS
Built-in categories seeded on first run.
- Source:
(constant) DEFAULT_WORK_LOCATION
Location id used for any day that has no stored value.
- Source:
(constant) EOD_SUBMIT_ENABLED :boolean
When true, the End of Day hours form shows a "submit" button that posts the entry through the local server (Claude in Chrome, then Playwright). When false (default) the form is a draft to copy from: hours and description are shown with a copy button and nothing is sent. Override in 00-config.local.js.
Type:
- boolean
- Default Value:
- false
- Source:
(constant) EPIC_STALE_DAYS
Days of inactivity after which an epic is offered up for archiving.
- Source:
(constant) GAP_REPORT_UTILITY_TEXTS :Set.<string>
Texts of utility entries logged via logUtilEntry() in 03-timer.js. They never carry documentation, so they would just be noise in the gap report. Exported so pure-fns-weeklyreport.js's buildWeeklyTicketSummary() can reuse the same exclusion rather than declaring a second copy.
Type:
- Set.<string>
- Source:
(constant) HIDDEN_MEETINGS_PREFIX
localStorage key prefix for the meetings a user has hidden on a given day.
- Source:
(constant) ICON_ACTIVITY
Activity-line SVG icon for the section header (Lucide-style, 24×24 viewBox, 1.5px stroke).
- Source:
JIRA_BASE :string
Base URL for Jira ticket links. Ticket keys found in task names are
converted to <a href="${JIRA_BASE}/${key}"> anchors.
Set to '' to disable link generation.
Override via setJiraBase() in src/js/00-config.local.js (gitignored) — copy
from src/js/00-config.local.example.js and set your real instance URL.
Type:
- string
- Source:
(constant) LONG_DAY_SPAN_MS
Day span, in ms, above which a workday is expected to contain a real break.
- Source:
(constant) MIGRATIONS :Array.<Migration>
Type:
- Array.<Migration>
- Source:
(constant) MIN_EXPECTED_BREAK_MS
Untracked time, in ms, below which a long day is flagged as missing a break.
- Source:
(constant) MS_PER_HOUR
Milliseconds in one hour, for converting tracked time to timesheet hours.
- Source:
(constant) PROTECTED_CAT_IDS :Array.<string>
Built-in epic IDs that must never be archived. They are seeded on first run
(DEFAULT_CATS in app-constants.js) and 'other' doubles as the fallback that
getCat() resolves unknown tags to, so removing it from the pickers would
leave orphaned entries with nothing to point at.
Type:
- Array.<string>
- Source:
(constant) RAPID_SIG_SHORTCUTS :Object.<string, string>
Signifier shortcode map for quick-capture inline tokens.
Maps !<shortcode> tokens to signifier values used on entry objects.
Unmapped tokens are left in the text unchanged.
Type:
- Object.<string, string>
- Source:
(constant) RETIRED_KEYS :Array.<RetiredKey>
localStorage keys belonging to features that have since been removed. Entries are never deleted from this list — a user upgrading across several versions still needs the key swept out of their browser.
Type:
- Array.<RetiredKey>
- Source:
(constant) SIG_SYMBOL
Signifier id → display symbol. Entries with no signifier show '●' (billable, the default).
- Source:
(constant) SIG_TITLE
Signifier id → accessible title. Entries with no signifier show 'Billable'.
- Source:
(constant) STORE_BLOCKS :string
localStorage key for the time-block array.
Type:
- string
- Source:
(constant) STORE_CATS
localStorage key for custom work categories.
- Source:
(constant) STORE_ENTRIES
localStorage key for logged work entries.
- Source:
(constant) STORE_LOCATION
localStorage key for the per-day work-location record.
- Source:
(constant) STORE_LOGNOTES
localStorage key for ad-hoc log notes.
- Source:
(constant) STORE_MIGRATION
localStorage key for the end-of-month migration record.
- Source:
(constant) STORE_POMO_LOG
localStorage key for the pomodoro session log.
- Source:
(constant) STORE_QP_HIDDEN
localStorage key for quick-pick items the user has dismissed.
- Source:
(constant) STORE_TIMER
localStorage key for the active timer state.
- Source:
(constant) STORE_TRACKERS
localStorage key for tracker progress grids.
- Source:
(constant) TF_PANE_IDS
Maps each view to the DOM id of the pane that hosts it.
- Source:
(constant) TF_STRIP_END
21:00 in minutes from midnight — right edge of the day-overview strip.
- Source:
(constant) TF_STRIP_START
07:00 in minutes from midnight — left edge of the day-overview strip.
- Source:
(constant) TF_VIEWS
Ordered list of view ids — drives the segmented control and keyboard nav.
- Source:
(constant) TF_VIEW_LABELS
Display labels for the segmented control. Static so we avoid recomputing on every render.
- Source:
(constant) TIMESHEET_ROUTE_NOTE
Status-line suffix per submission route the server reports.
- Source:
WEATHER_LAT :number
Latitude of the work location (decimal degrees). Default used when the server is not running; normally set via config.local.ps1.
Type:
- number
- Source:
WEATHER_LON :number
Longitude of the work location (decimal degrees). Default used when the server is not running; normally set via config.local.ps1.
Type:
- number
- Source:
WEATHER_NAME :string
Display name for the work location shown next to the weather widget. Default used when the server is not running; normally set via config.local.ps1.
Type:
- string
- Source:
(constant) WEEKLY_REPORT_NO_TICKET_KEY
Sentinel ticket key for entries whose text carries no parseable Jira prefix.
- Source:
(constant) WORK_LOCATIONS :Readonly.<Record.<string, {emoji: string, label: string}>>
Work-location presets keyed by their stored id. Each entry carries the emoji and human-readable label shown in the date-nav header. The first key is the default applied to any day with no stored location.
Type:
- Readonly.<Record.<string, {emoji: string, label: string}>>
- Source:
_boardDragTaskId
Shared drag-state for board column DnD (set by dragstart, read by drop).
- Source:
_exportBackupFn
Registered exportBackup function, injected at startup to avoid a non-leaf dep.
- Source:
_flowNoteEditId
Task ID whose note is currently being edited inline in the Flow view.
- Source:
_heroStopped :boolean
Type:
- boolean
- Source:
_heroStoppedEntry :Object|null
The log entry that was just stopped. Kept so the Undo action can recover it and the stopped panel can display the correct task name / session range.
Type:
- Object | null
- Source:
_heroStoppedTimer :number|null
Type:
- number | null
- Source:
(constant) _invalidListenerPending :WeakSet.<HTMLInputElement>
Tracks inputs that already have a pending clear-on-edit listener, so that calling markInputInvalid() repeatedly before the user edits does not stack multiple listeners on the same element.
Type:
- WeakSet.<HTMLInputElement>
- Source:
_qcFilterCat :string|null
Active category chip selection. Serves two purposes:
- filters the task list to show only that category, and
- tags any ad-hoc log entry created via "Log without tracking". null means "All" (no filter; entry falls back to selectedTag).
Type:
- string | null
- Source:
_qcSearch :string
Type:
- string
- Source:
_qcTickInterval :number|null
Type:
- number | null
- Source:
_rapidOpen :boolean
Type:
- boolean
- Source:
_renderFn
Registered render function, injected at startup to avoid a non-leaf dep.
- Source:
catManageOpen
Controls whether the epic manage row (rename/delete/add) is expanded.
- Source:
(constant) copyButtonOriginalLabels
Label markup each copy button had before its first "Copied!" flash.
- Source:
doneHistoryOpen
Whether the Done column's older-history expander is open.
- Source:
emergencyMode :boolean
True while the focus/emergency overlay is visible.
Type:
- boolean
- Source:
wipWarnDismissed
Whether the WIP-over warning banner has been dismissed this render cycle.
- Source:
Methods
_heroBindCatPicker(wrap)
Binds open/close/select keyboard and pointer events on a .hero-cat-wrap element.
Parameters:
| Name | Type | Description |
|---|---|---|
wrap |
HTMLElement | The |
- Source:
_heroCancelStoppedTimer()
Cancels the auto-dismiss timer (called by Undo / Done buttons).
- Source:
_heroCatSelect(newTag)
Updates the active entry's category, persists, and re-renders.
Parameters:
| Name | Type | Description |
|---|---|---|
newTag |
string | Category ID to apply. |
- Source:
_heroFillIdle()
Updates the idle panel: logged-today total and last-session time.
- Source:
_heroFillPaused()
Updates the paused panel with the frozen clock and task details.
- Source:
_heroFillRunning()
Updates the running panel: category dot + task title + started-at sub-line.
- Source:
_heroFillStopped()
Updates the stopped panel with the session summary.
- Source:
_heroHandleDone()
Dismiss the stopped confirmation panel immediately (same as auto-dismiss).
- Source:
_heroHandleStart()
Handles the "▶ Start tracking" button. Uses the composer input if it has text; otherwise focuses the plan input.
- Source:
_heroHandleUndo()
Undo the just-stopped entry: remove it from entries and optionally restart the timer if the entry had been running (i.e. it had no prior tsEnd).
- Source:
_heroLastNoteText(entryId) → {string}
Returns "↳ last note X ago" text for the most recent session-note on an entry, or an empty string when no session notes have been added yet.
Parameters:
| Name | Type | Description |
|---|---|---|
entryId |
string | ID of the active log entry. |
- Source:
Returns:
- Type
- string
_heroRenderRecentChips()
Builds the recent-task chip strip in the idle panel. Shows up to 3 distinct recent entries with their category dot.
- Source:
_heroSessionCount(entry) → {number}
Returns the number of distinct time entries today for the same task text.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object |
- Source:
Returns:
- Type
- number
_heroSetCategory(elId, tag, interactiveopt)
Fills a category display cell with the dot + label (and optional caret/picker).
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
elId |
string | ID of the |
||
tag |
string | Category ID. |
||
interactive |
boolean |
<optional> |
false | When true, renders a caret button and picker panel. |
- Source:
_heroShowPanel(id, visible)
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string | Element ID. |
visible |
boolean | Whether to show the element. |
- Source:
_heroStartFromChip(text, tag)
Starts tracking from a recent-chip click. Re-uses the existing entry (no duplicate) and starts the timer.
Parameters:
| Name | Type | Description |
|---|---|---|
text |
string | Task description. |
tag |
string | Category ID. |
- Source:
_qcActivateRow(rowId, text, tag, isActive)
Starts or switches to a task from the quick-capture list. If the row is already the active timer, just closes the modal. For plan-task rows a fresh time entry is created.
Parameters:
| Name | Type | Description |
|---|---|---|
rowId |
string | Row ID from the data attribute. |
text |
string | Task description. |
tag |
string | Category ID. |
isActive |
boolean | Whether this is the currently-running task. |
- Source:
_qcBindTaskListEvents(el)
Attaches click listeners to task-row action buttons and their parent rows. Separated from rendering so each has a single responsibility.
Parameters:
| Name | Type | Description |
|---|---|---|
el |
HTMLElement | The #qcTaskList container element. |
- Source:
_qcBuildTaskGroups(searchLower, todayKey) → {Object}
Collects and deduplicates the three task groups for the current filter state. Pure data function — performs no DOM operations.
Parameters:
| Name | Type | Description |
|---|---|---|
searchLower |
string | Lower-cased search string; empty string means no filter. |
todayKey |
string | Date key for today in YYYY-MM-DD format. |
- Source:
Returns:
- Type
- Object
_qcLogOnly()
Handles the "Log without tracking" footer button and Enter keypress.
Parses inline shorthand tokens from the input before creating the entry:
#<cat> overrides category, !<sig> sets the signifier,
><date> sets the entry date (today / tomorrow / YYYY-MM-DD / weekday).
- Non-empty text after token stripping → creates a log entry (no timer) and closes.
- Only tokens, no text → refocuses the input so the user adds a description.
- Completely empty input → closes and focuses the ad-hoc row in the time log.
- Source:
_qcRenderAll()
Re-renders all dynamic regions of the quick-capture modal.
- Source:
_qcRenderCatChips()
Renders the "All" chip plus one chip per category. The active chip is highlighted; clicking a chip filters the task list.
- Source:
_qcRenderRunningStrip()
Shows or hides the running strip. When a timer is active the strip displays the current task and elapsed time. When idle the strip is hidden and the pulsing-dot class is removed.
- Source:
_qcRenderTaskList()
Orchestrates data collection, rendering, and event binding for the task list. Delegates each concern to a single-purpose helper.
- Source:
_qcRenderTokenPreview(parsed)
Renders colored pill badges below the search input for any recognised inline tokens in the current input value. Hides the preview container when no tokens are active (e.g. on modal open or after the field is cleared).
Parameters:
| Name | Type | Description |
|---|---|---|
parsed |
Object | Result of parseRapidTokens() for the current raw input value. |
- Source:
_qcStartTick()
Starts a 1-second interval to update the elapsed-time label in the running strip.
- Source:
_qcStopTick()
Clears the running-strip interval.
- Source:
_qcTaskListHtml(groups, search) → {string}
Renders the task list HTML string from pre-built group data. Receives all inputs as parameters — performs no DOM or module-state reads.
Parameters:
| Name | Type | Description |
|---|---|---|
groups |
Object | |
search |
string | Original (un-lowercased) search string for the empty-state message. |
- Source:
Returns:
HTML string ready for assignment to el.innerHTML.
- Type
- string
_qcTaskRowHtml(rowId, text, cat, isActive) → {string}
Returns the HTML for one task row in the task list. The action button is opacity-0 and revealed on row hover via CSS.
Parameters:
| Name | Type | Description |
|---|---|---|
rowId |
string | Unique row key: entry ID or 'plan:' + plan-task ID. |
text |
string | Task/entry description text. |
cat |
Object | Resolved category. |
isActive |
boolean | True when this row matches the currently-running entry. |
- Source:
Returns:
HTML string.
- Type
- string
_qcUpdateElapsed()
Updates only the elapsed-time label; called every second by the ticker.
- Source:
activeTimerDurationMs(entry) → {number}
Returns the effective tracked duration (ms) of an active-timer entry, honouring pause state — matches the formula used by renderFlowHeader so the strip bar and Flow-view duration freeze while paused.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
object |
- Source:
Returns:
0 if no timer is active for this entry.
- Type
- number
addEntry(withTimer)
Creates a new log entry from the capture input's current value.
Optionally starts the timer on the new entry (withTimer = true), in which
case any running timer is stopped first and the matching plan task is
auto-promoted to "in progress".
Parameters:
| Name | Type | Description |
|---|---|---|
withTimer |
boolean | If true, start the timer on the new entry. |
- Source:
addLogNote()
Reads the note input, appends a note to logNotes, persists, and re-renders.
- Source:
addPlanTask() → {void}
Reads the plan-input field and adds a new "todo" task to today's plan. Inherits the currently selected tag and that category's billable default. No-ops if the input is empty.
- Source:
Returns:
- Type
- void
(async) addTaskToNotion(task) → {Promise.<string>}
Adds a work-log task to Notion as a child page under the matching project.
The project is looked up server-side by matching the task's category label to a
Notion project's Epic field. Uses /api/notion-add-task (Notion REST API, no AI).
Parameters:
| Name | Type | Description |
|---|---|---|
task |
Object | Plan task object with at least |
- Source:
Throws:
-
If the API call fails or no URL is returned.
- Type
- Error
Returns:
The URL of the newly created Notion page.
- Type
- Promise.<string>
annotateBillableStatus(entries) → {Array.<Object>}
Returns copies of entries each carrying its resolved billable status as a
_billable flag. Pure helpers such as findGapReportEntries() have no access
to the category/task lookups isEntryBillable() needs, so callers resolve the
status up front and hand it over on the entry. The input entries are not
mutated.
Parameters:
| Name | Type | Description |
|---|---|---|
entries |
Array.<Object> | Log entries to annotate. |
- Source:
Returns:
New entry objects with _billable set.
- Type
- Array.<Object>
applyBackupRetention(entries, retentionDays, nowMs) → {Object}
Filters an entries array to those whose date field falls within the
retention window (today minus retentionDays, inclusive). Entries with a
missing or unparseable date are excluded to keep backups clean.
The cutoff is computed from nowMs so the function stays pure and testable.
Parameters:
| Name | Type | Description |
|---|---|---|
entries |
Array.<{date: (string|undefined)}> | Raw entries array from localStorage. |
retentionDays |
number | How many days back to keep (the app uses 21). |
nowMs |
number | Current time as a Unix timestamp in milliseconds. |
- Source:
Returns:
The filtered entries and count of dropped ones.
- Type
- Object
Example
applyBackupRetention(entries, 21, Date.now())
// → { retainedEntries: [...], dropped: 12 }
applyEpicArchive(categories, staleIds) → {Array.<EpicCategory>}
Returns a copy of categories with archived: true stamped on every epic
whose ID is in staleIds. Non-destructive: no record is dropped and every
other field is preserved untouched.
Parameters:
| Name | Type | Description |
|---|---|---|
categories |
Array.<EpicCategory> | All known epics. |
staleIds |
Array.<string> | Epic IDs to archive. |
- Source:
Returns:
A new array with the archived epics flagged.
- Type
- Array.<EpicCategory>
applyTime(baseTsMs, timeStr) → {number}
Replaces the hours/minutes of a base timestamp with values parsed from a "HH:MM" string, returning the resulting timestamp in milliseconds.
Parameters:
| Name | Type | Description |
|---|---|---|
baseTsMs |
number | Base Unix timestamp (ms) that supplies the date. |
timeStr |
string | Time string in "HH:MM" format. |
- Source:
Returns:
New Unix timestamp (ms) with the updated time.
- Type
- number
autoCarryTasks() → {number|undefined}
Carries unfinished plan tasks from past days into today — runs once per day
(guarded by a localStorage flag). Deduplicates by text, preserving the most
recent past status and status-comment history. Checkpoints are carried forward
with done reset to false for a fresh day.
- Source:
Returns:
Number of tasks newly carried, or undefined if carry has already run today.
- Type
- number | undefined
billBtnHtml(t, status) → {string}
Builds the billable toggle button HTML for a task row. Returns empty string for pending/blocked/upcoming tasks where billing is irrelevant.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
Object | The plan task. |
status |
string | The task's current status. |
- Source:
Returns:
HTML button element, or ''.
- Type
- string
bindAdHocRow() → {void}
Binds click/Enter handlers for the ad-hoc inline log row (#tlAdHocBtn /
#tlAdHocInput). Called from renderTimelineSection() on both the
empty-state branch and the normal entry-list branch, since #timeline's
innerHTML — and the row inside it — is fully replaced on every render()
call. Both branches must call this, or the row exists in the DOM but
silently does nothing.
- Source:
Returns:
- Type
- void
bindBoardColumnDnD() → {void}
Makes each rendered board card draggable and wires its dragstart/dragend.
Called once per renderPlan() cycle after columns are populated.
Static column drop-zone listeners are set up once in initBoardColumnDnD().
- Source:
Returns:
- Type
- void
bindEntryMetaEvents(timelineEl)
Binds open/save/clear/cancel events for each entry's proof-link/note editor, plus the yes/no handlers for its restart note-confirmation banner (see createRestartedEntry in 05-entries.js). Re-attached after every render() call since #timeline's innerHTML is fully replaced each time, same pattern as the other entry-row bindings in render() (time editor, category picker, billable toggle).
Parameters:
| Name | Type | Description |
|---|---|---|
timelineEl |
HTMLElement | The #timeline element. |
- Source:
bindEpicsManager() → {void}
Wires the epics modal: the toolbar button that opens it, the tidy action, and the close button. Called once at startup (12c-startup.js).
These controls used to live in the epic manage strip inside #tagRow, which
is display:none in work-log.html — so archiving could be triggered from
five pickers but never undone. The modal is the reachable home for them.
- Source:
Returns:
- Type
- void
bindFlowNoteEvents(pane)
Binds note edit/save/remove/cancel events on the Flow pane after each render. Listeners are attached to freshly-rendered child elements; prior children are discarded by the innerHTML replacement in renderFlowView, so there are no orphan listeners. Both renderFlowView and renderPlan are called after mutations so the Flow pane and the kanban board stay in sync.
Parameters:
| Name | Type | Description |
|---|---|---|
pane |
HTMLElement | The #tfFlowPane element. |
- Source:
bindMonthlyCalendarEvents(calEl) → {void}
Binds the calendar's interactions inside calEl: clicking a day cell
navigates to that day's Log view, and the prev/next buttons move the viewed
month.
Implicit dependency: the prev/next handlers mutate module-level _mlYear /
_mlMonth and then call renderMonthlyLog() to re-render the whole view.
Both globals must therefore be in scope when this function is called.
Parameters:
| Name | Type | Description |
|---|---|---|
calEl |
HTMLElement | The |
- Source:
- See:
Returns:
- Type
- void
bindPlanCheckpointEvents(qa) → {void}
Binds the per-card checkpoint editor handlers (open/close badge, three-state
done toggles, inline edit, delete, add-on-Enter, and drag-to-reorder).
Called from bindPlanEvents() after each render.
Parameters:
| Name | Type | Description |
|---|---|---|
qa |
function | Selector helper closed over the rendered column lists. |
- Source:
Returns:
- Type
- void
bindPlanCommentEvents(qa) → {void}
Binds the per-card status-comment editor handlers (accept/skip/edit input,
plus the comment-history expander). Called from bindPlanEvents() after
each render.
Parameters:
| Name | Type | Description |
|---|---|---|
qa |
function | Selector helper closed over the rendered column lists. |
- Source:
Returns:
- Type
- void
bindPlanEvents(lists)
Binds all plan list event handlers after each render.
Parameters:
| Name | Type | Description |
|---|---|---|
lists |
Array.<HTMLElement> | Column list elements (To Do, In Progress, Done). |
- Source:
bindPlanNoteEvents(qa) → {void}
Binds the per-card note editor handlers (toggle, display-to-edit, save,
delete, cancel, and textarea keyboard shortcuts). Called from
bindPlanEvents() after each render.
Parameters:
| Name | Type | Description |
|---|---|---|
qa |
function | Selector helper closed over the rendered column lists. |
- Source:
Returns:
- Type
- void
bindSignifierClicks()
Attaches click and keyboard listeners to all .esig elements after a render.
- Source:
bindTagRowEvents() → {void}
Wires every listener for the markup buildTagRowHtml() just produced.
Each lookup past the always-present dropdown controls is null-guarded: only one inline mode is in the DOM at a time, so most of these elements are absent on any given render.
- Source:
Returns:
- Type
- void
bindTimelineEntryEvents(timelineEl) → {void}
Binds every per-entry event handler in the timeline: the time editor, the category picker (including its inline "+ new epic" creator), the billable toggle, delete, restart, and inline text rename. Called once per renderTimelineSection() call on the non-empty branch, since #timeline's innerHTML is fully replaced on every render() call.
Parameters:
| Name | Type | Description |
|---|---|---|
timelineEl |
HTMLElement | The #timeline element. |
- Source:
Returns:
- Type
- void
blockAriaLabel(block) → {string}
Accessible name for a planned block. A bare focusable div is dropped from
the accessibility tree unless it has a role, so the block is also given
role="group"; the name carries the time range and says how to move it,
which screen-reader users cannot otherwise discover.
Parameters:
| Name | Type | Description |
|---|---|---|
block |
Object | The planned block. |
- Source:
Returns:
Text such as "Write report — planned 11:00–12:00, press arrow keys to move".
- Type
- string
blockMoveDelta(key) → {number}
Slot change a key press asks for.
Parameters:
| Name | Type | Description |
|---|---|---|
key |
string | A |
- Source:
Returns:
-1 for ArrowUp (earlier), 1 for ArrowDown (later), 0 for any other key.
- Type
- number
buildBackupPayload(state, retentionDays, nowMs) → {Object}
Builds the JSON-backup payload with the retention window applied to every time-series array, so the backup file size stays bounded over time.
Previously only entries was trimmed while planTasks, blocks, devLog,
and distractions were bundled in full — those grow every day (carried
tasks, per-day time blocks, appended distraction/dev-log records) and were
the reason "21-day" backups kept getting bigger. Each of those arrays carries
a YYYY-MM-DD date field, so they prune through the same window as
entries.
Non-time-series data is intentionally kept whole: categories and qpHidden
are small reference sets with no date, and pomoLog is already capped at
source (100 records) so it needs no further trimming here.
Kept pure (no localStorage, no ambient clock) so the bounded-size guarantee
is unit-testable — the caller supplies the current state and the clock.
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
state |
object | Current application state arrays. Properties
|
||||||||||||||||||||||||||||||||||||
retentionDays |
number | How many days back to keep for dated arrays. |
||||||||||||||||||||||||||||||||||||
nowMs |
number | Current time as a Unix timestamp in milliseconds. |
- Source:
Returns:
The backup
payload object and a per-array count of records excluded from it (only
arrays that dropped at least one record are included). The count folds
together records that aged out of the window and records dropped for a
missing/malformed date — both are legitimately excluded, so they are not
distinguished here.
- Type
- Object
Example
buildBackupPayload({ entries, planTasks, blocks }, 21, Date.now())
// → { payload: { version: '1', entries: [...], planTasks: [...], ... }, dropped: { blocks: 4 } }
buildBlockActionButtonsHtml(block) → {string}
Builds the action buttons shown on a planned block: start (task blocks only), add/change emoji, and delete. Each button's accessible name includes the block's text because a glyph or "▶ start" alone is announced identically on every block, so screen-reader users cannot tell which one they are on (WCAG 4.1.2 / 2.4.6).
Parameters:
| Name | Type | Description |
|---|---|---|
block |
Object | The planned block. |
- Source:
Returns:
HTML for the buttons, ready to append inside the block element.
- Type
- string
buildDailyLogItems(dateKey) → {Array.<{ts: number, type: string, entryId: (string|undefined), parentEntryId: (string|undefined), color: string, text: string, sub: string}>}
Builds a chronologically sorted array of feed items for the Log view. Merges time entries, log notes, and task status comments for the given day.
Item types:
'entry'— a time-tracked entry; includesentryId.'note'— a freeform log note; text is wrapped in<em>.'session-note'— a note attached to a running/completed entry; includesparentEntryId. Renderers nest these inside their parent entry row rather than as standalone timeline rows.'task'— a plan-task status comment.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | YYYY-MM-DD date string. |
- Source:
Returns:
- Type
- Array.<{ts: number, type: string, entryId: (string|undefined), parentEntryId: (string|undefined), color: string, text: string, sub: string}>
buildEntryCatPickerHtml(entry, categoryList) → {string}
Builds the category picker contents for a single log entry: one button per existing category, a cancel button, and an inline "+ new epic" creator so a brand-new epic can be added without leaving the entry — mirrors the board's task-card picker (catOpts in 10a-tasks-row.js's renderRow), which previously was the only place epics could be created.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | The log entry. |
categoryList |
Array.<{id: string, label: string, color: string}> | Available categories. |
- Source:
Returns:
HTML string for the picker's contents.
- Type
- string
buildEntryLinkMap(dayEntries) → {Object.<string, string>}
Builds a lookup of proof links for a single day, keyed by lowercased task text so it lines up with buildEntryNoteMap and the task keys produced by groupEntriesByCategory. Unlike notes (free text, one line per entry), links are short reference codes — Zephyr keys, Confluence pages, ticket URLs — so multiple links for the same task are deduplicated and joined onto a single comma-separated line rather than one per entry.
Parameters:
| Name | Type | Description |
|---|---|---|
dayEntries |
Array.<Object> | Entries for the exported day. |
- Source:
Returns:
Map of lowercased task text to a comma-separated, deduplicated list of that task's proof links.
- Type
- Object.<string, string>
Example
buildEntryLinkMap([
{ text: 'Update test steps', link: 'T197797' },
{ text: 'Update test steps', link: 'T197805' },
])
// → { 'update test steps': 'T197797, T197805' }
buildEntryMetaHtml(entry, isEditing) → {string}
Builds the proof-link / note indicator and inline editor for a log entry.
Read-only state shows a 🔗 and/or 📝 indicator (title = full value) plus a
toggle button; editing state additionally shows the link/note inputs. Both
fields are optional and independent — either can be filled in on its own.
When this entry has a pending restart confirmation (_pendingNoteConfirm,
set by createRestartedEntry in 05-entries.js), the editor also shows a
"same note as last time?" banner above the inputs.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | The log entry. |
isEditing |
boolean | Whether this entry's link/note panel is open. |
- Source:
Returns:
HTML string.
- Type
- string
buildEntryNoteMap(dayEntries) → {Object.<string, string>}
Builds a lookup of entry-level notes for a single day, keyed by lowercased task text so it lines up with buildTaskNoteMap and the task keys produced by groupEntriesByCategory. Entry notes are written at the time the work happens (unlike a plan task's note, which describes the task in general), so multiple entries sharing a task text each contribute their own note line rather than overwriting one another.
Parameters:
| Name | Type | Description |
|---|---|---|
dayEntries |
Array.<Object> | Entries for the exported day. |
- Source:
Returns:
Map of lowercased task text to newline-joined notes, one line per entry that carries a note.
- Type
- Object.<string, string>
Example
buildEntryNoteMap([{ text: 'Fix login', note: 'reproduced in staging' }])
// → { 'fix login': 'reproduced in staging' }
buildFlowTaskNoteHtml(task, isEditing) → {string}
Builds the note display / edit widget for a task-type flow row. Shows read-only text when not editing; shows a textarea when editing.
Parameters:
| Name | Type | Description |
|---|---|---|
task |
Object | The plan task object. |
isEditing |
boolean | Whether this task's note is being edited. |
- Source:
Returns:
HTML string.
- Type
- string
buildGapReportRowHtml(entry) → {string}
Builds one gap-report row: date label, task text (with any leading Jira key linkified), and a "+ fix" button that jumps to the entry.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | A flagged log entry (from findGapReportEntries()). |
- Source:
Returns:
HTML string.
- Type
- string
buildManageRowHtml(selCat) → {string}
Builds the inner markup of the epic manage row for whichever inline mode is active. The modes are mutually exclusive and tested in priority order: rename, add-new, then the idle button strip.
Reads the module-level mode flags (editingCatId, addingNewCat) rather than taking them as parameters, matching how the rest of this concatenated file owns that state. Archive and restore are not handled here - #385 moved them into the epics manager modal (renderEpicsManager/bindEpicsManager above).
Parameters:
| Name | Type | Description |
|---|---|---|
selCat |
EpicCategory | The currently selected epic, already colour-sanitised by getCat(). |
- Source:
Returns:
HTML for the inside of #catManageRow.
- Type
- string
buildMeetingButtonsHtml(meeting, index) → {Object}
Builds the two per-meeting buttons ("▶ start" and "✕ hide"). Both carry an aria-label naming the meeting: the visible glyph text is identical on every row, so without it a screen-reader user cannot tell which meeting a button acts on (WCAG 4.1.2 / 2.4.6).
Parameters:
| Name | Type | Description |
|---|---|---|
meeting |
Object | The meeting the buttons act on. |
index |
number | Position of the meeting in the rendered list; the hide handler reads it back. |
- Source:
Returns:
HTML for each button.
- Type
- Object
buildMonthlyCalendarHtml(year, month) → {string}
Builds the heatmap calendar markup: navigation header, day labels, day cells
and the colour legend. Pure string building (reads hours per day through
mlHoursForDay); touches no DOM.
Day cells are rendered as <button> elements so keyboard users can reach
them with Tab and activate them with Enter or Space (WCAG 2.1.1 Keyboard).
The prev/next buttons carry aria-label values that include the target
month name, satisfying WCAG 4.1.2 Name, Role, Value.
Parameters:
| Name | Type | Description |
|---|---|---|
year |
number | Full year to render. |
month |
number | Month index, 0-based. |
- Source:
Returns:
HTML for the calendar container's contents.
- Type
- string
buildPlanReviewRowHtml(task) → {string}
Builds one checklist row: date, ticket key (via the existing parseJiraLabel()) or plain text, and "done"/"drop" reconcile actions.
Parameters:
| Name | Type | Description |
|---|---|---|
task |
Object | A plan task from findWeeklyPlanReviewTasks(). |
- Source:
Returns:
HTML string.
- Type
- string
buildRollingSummary(dateKeys, opts) → {Array.<{dateKey: string, locationEmoji: string, sodTs: (number|null), eodTs: (number|null), totalMs: number, topTasks: Array.<{text: string, totalMs: number}>}>}
Computes a rolling per-day summary for a set of date keys.
All data dependencies are injected so the function is pure and unit-testable without any browser globals or localStorage access.
Parameters:
| Name | Type | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dateKeys |
Array.<string> | Date keys (YYYY-MM-DD) to summarise, most recent first. |
|||||||||||||||
opts |
object |
Properties
|
- Source:
Returns:
- Type
- Array.<{dateKey: string, locationEmoji: string, sodTs: (number|null), eodTs: (number|null), totalMs: number, topTasks: Array.<{text: string, totalMs: number}>}>
Example
buildRollingSummary(['2026-06-04'], {
entries: [{ date: '2026-06-04', text: 'Write code', ts: 1000, tsEnd: 4600000, signifier: '' }],
getDayStartTs: () => 1000,
getDayEodTs: () => null,
getLocationEmoji: () => '🏠',
})
// → [{ dateKey: '2026-06-04', locationEmoji: '🏠', sodTs: 1000, eodTs: null,
// totalMs: 4599000, topTasks: [{ text: 'Write code', totalMs: 4599000 }] }]
buildSessionNotesHtml(notes) → {string}
Builds the HTML fragment for a list of session-notes nested under a parent entry row. Returns an empty string when there are no notes.
Parameters:
| Name | Type | Description |
|---|---|---|
notes |
Array.<object> | Session-note items for one parent entry. |
- Source:
Returns:
HTML string, or '' if notes is empty.
- Type
- string
buildStatSubHtml(label, ms) → {string}
Builds the HTML for a sub-stat tile's task line: a Jira ticket link (when the label parses as one) plus the task name, or the raw label when it doesn't, followed by the formatted duration.
Parameters:
| Name | Type | Description |
|---|---|---|
label |
string | Task text, possibly Jira-prefixed. |
ms |
number | Tracked duration in milliseconds. |
- Source:
Returns:
HTML string for the tile body.
- Type
- string
buildSummaryText(rows, weekTotalMs, locationMap) → {string}
Builds the plain-text standup block from computed rows. Pure function — no DOM or clipboard access.
Parameters:
| Name | Type | Description |
|---|---|---|
rows |
Array.<object> | Output of buildRollingSummary. |
weekTotalMs |
number | Sum of all days' tracked time in ms. |
locationMap |
Record.<string, string> | Pre-read location map. |
- Source:
Returns:
- Type
- string
buildTagRowHtml() → {string}
Builds the complete #tagRow markup: the epic dropdown row plus the manage row. Touches no DOM of its own - it reads module state and returns a string, so the markup can be produced and asserted on independently of the listener wiring in bindTagRowEvents().
- Source:
Returns:
HTML to assign to #tagRow.
- Type
- string
buildTaskNoteMap(planTasks, dateKey) → {Object.<string, string>}
Builds a lookup of task notes for a single day, keyed by lowercased task
text so it lines up with the task keys produced by
groupEntriesByCategory. Only tasks dated dateKey with a non-blank
note are included.
Task/entry linkage is by date + case-insensitive text match — the same
convention addEntry and flatSort already use to find a task's plan row —
because plan tasks and log entries share no taskId field.
Parameters:
| Name | Type | Description |
|---|---|---|
planTasks |
Array.<Object> | All plan/board tasks. |
dateKey |
string | The exported day's date key (YYYY-MM-DD). |
- Source:
Returns:
Map of lowercased task text to trimmed note.
- Type
- Object.<string, string>
Example
buildTaskNoteMap(
[{ text: 'Fix login', date: '2026-06-04', note: 'waiting on staging creds' }],
'2026-06-04'
)
// → { 'fix login': 'waiting on staging creds' }
buildTimesheetDayPayload(dateKey, timedEntries, categoryLabel) → {Object|null}
Builds the one whole-day timesheet entry: the date, total tracked hours (rounded to two decimals, as timesheet forms take decimal hours) and the description from buildTimesheetDescription. All tracked time counts, billable or internal, because the timesheet records the full working day.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | Day being submitted, |
timedEntries |
Array.<Object> | The day's entries ( |
categoryLabel |
function | Category id → display label. |
- Source:
Returns:
The entry,
or null when nothing was tracked (so the caller can skip submitting).
- Type
- Object | null
buildTimesheetDescription(timedEntries, categoryLabel) → {string}
Builds the draft timesheet description in the house style: one
activity (task, task) group per category in first-seen order, groups
comma-separated, no durations — e.g.
ticket review (AITO-1, AITO-2), meeting (FUAT bi-daily). The activity
wording a person writes by hand ("UAT preparation and test design") is not
in the log, so the category label stands in for it and the caller offers the
draft for editing. Task labels keep their Jira key because that is what a
reader checks against the ticket; they are de-duplicated case-insensitively
within a group so a task worked in two blocks appears once.
Parameters:
| Name | Type | Description |
|---|---|---|
timedEntries |
Array.<Object> | The day's tracked entries ( |
categoryLabel |
function | Maps a category id to its
display label (e.g. |
- Source:
Returns:
The draft description, or '' when there are no entries.
- Type
- string
Example
buildTimesheetDescription(
[{ text: 'AITO-1', tag: 'review' }, { text: 'AITO-2', tag: 'review' }, { text: 'FUAT', tag: 'meeting' }],
(id) => id
)
// → 'review (AITO-1, AITO-2), meeting (FUAT)'
buildTimesheetSummaryLine(timedEntries, fmtDuration) → {string}
Builds the pasteable end-of-day summary line: one semicolon-separated
Label (duration) item per distinct task, each carrying its full-day
total — not per-session — so a task worked in two separate blocks (e.g.
09:30–13:00 and 13:30–16:00) still collapses to one line with one total,
matching how a Jira worklog is checked against a ticket. The per-session
breakdown lives in the report body instead (see formatGroupedLines's
sessions rendering) — this line's job is the checkable total, not the
timeline. Distinct tasks are kept in first-seen order; entries are grouped
by task text, category (tag, missing normalised to other), and
_billable status combined, so a task worked once billable and once
internal renders as two line items rather than one status silently
overwriting the other.
Non-billable items are suffixed , internal so the billable/internal split
— already visible in the header totals — is also checkable at the level of
an individual line item.
Unlike the grouped report body (whose task labels feed a client-facing read), this line keeps the entry's raw text, Jira key included: the line's job is proving logged hours against ticket worklogs, so the ticket key is exactly the part that must stay visible.
Parameters:
| Name | Type | Description |
|---|---|---|
timedEntries |
Array.<Object> | Timed entries ( |
fmtDuration |
function | Formats a duration in ms
(e.g. |
- Source:
Returns:
The summary line, or '' when there are no entries.
- Type
- string
Example
buildTimesheetSummaryLine(
[
{ text: 'AITO-183656', ts: 0, tsEnd: 4 * 3600000 },
{ text: '📅 Meeting', ts: 4 * 3600000, tsEnd: 4.5 * 3600000, _billable: false },
{ text: 'AITO-183656', ts: 4.5 * 3600000, tsEnd: 7.5 * 3600000 },
],
fmtDurLong
)
// → 'AITO-183656 (7h); 📅 Meeting (30min, internal)'
buildWeeklyTicketSummary(entries, weekStart, weekEnd) → {Object}
Groups a week's finished, non-cancelled, non-utility entries by Jira ticket key (via parseJiraLabel), then by task name within each ticket — the data layer behind the weekly report draft. Entries with no parseable ticket prefix fall into the WEEKLY_REPORT_NO_TICKET_KEY bucket, sub-grouped by their own task text so untracked work stays legible rather than collapsing into one opaque blob.
Uses the same "finished, not cancelled, in-window" filter as
findGapReportEntries (whose GAP_REPORT_UTILITY_TEXTS exclusion
it also reuses — break/lunch/meeting entries never belong in a status
report any more than they belong in the gap report).
Parameters:
| Name | Type | Description |
|---|---|---|
entries |
Array.<Object> | All log entries. |
weekStart |
number | Inclusive week-start timestamp in ms (e.g. Monday 00:00). |
weekEnd |
number | Exclusive week-end timestamp in ms (e.g. the following Monday 00:00). |
- Source:
Returns:
ticketOrder lists real ticket keys sorted by total time descending,
with the no-ticket bucket (if present) always last regardless of its
total — it is a catch-all, not a reportable line item.
- Type
- Object
Example
buildWeeklyTicketSummary(
[{ id: '1', text: 'PROJ-1: Fix login', ts: 100, tsEnd: 3700, date: '2026-06-01' }],
0, 604800000
)
// → { ticketOrder: ['PROJ-1'], grouped: { 'PROJ-1': { totalMs: 3600, ... } } }
calAccountLabel(account) → {string|null}
Resolves a human-readable account label for an Outlook calendar account.
The PowerShell server sends the raw DisplayName which may be an email
address, a display name, or free-form company text. Tries three strategies
in order: exact match, email domain extraction, and substring match.
Parameters:
| Name | Type | Description |
|---|---|---|
account |
string | null | Raw Outlook account identifier. |
- Source:
Returns:
Display label (e.g. "Example Corp"), or null if unknown.
- Type
- string | null
calcMonthSummaryStats(allEntries, monthPrefix, isBillable) → {Object}
Computes monthly summary statistics from a flat entries array. Pure: takes
its own data dependency, no DOM, no module globals. Excludes entries with
no tsEnd (still running) or signifier === 'cancelled'.
Parameters:
| Name | Type | Description |
|---|---|---|
allEntries |
Array.<Object> | The full entries array to filter. |
monthPrefix |
string | Date prefix |
isBillable |
function | Predicate identifying
billable entries; the caller supplies the project's |
- Source:
Returns:
- Type
- Object
calcMonthTaskCounts(allTasks, monthPrefix) → {Object}
Computes open / done / migrated task counts for a given month. Pure.
_migrated (programmatic) and signifier === 'migrated' (BuJo marker)
both count toward the migrated total.
Parameters:
| Name | Type | Description |
|---|---|---|
allTasks |
Array.<Object> | The full plan-tasks array. |
monthPrefix |
string | Date prefix |
- Source:
Returns:
- Type
- Object
calcStreak(allEntries) → {number}
Counts consecutive days with at least one logged entry, looking backwards from yesterday. Today is excluded so the streak only increments once the day has been completed.
Parameters:
| Name | Type | Description |
|---|---|---|
allEntries |
Array.<{date: string}> | The full entries array. |
- Source:
Returns:
The current streak length (0 if no streak).
- Type
- number
calendarMeetingKey(meeting) → {string}
Returns the stable per-occurrence key for a meeting.
Subject alone is not an identity: a recurring meeting has the same subject at every occurrence, so anything keyed on it treats them all as one. Pairing the subject with the start time identifies the single occurrence on screen.
Parameters:
| Name | Type | Description |
|---|---|---|
meeting |
* | Meeting object from the calendar API response. |
- Source:
Returns:
Key of the form subject|start, or '' for a non-object.
- Type
- string
Example
calendarMeetingKey({ subject: 'Standup', start: '2026-05-28T09:00' })
// → 'Standup|2026-05-28T09:00'
(async) callClaudeWithNotion(prompt, optsopt) → {Promise.<string>}
Calls the Claude API with a Notion MCP server attached, via the local proxy
at /api/notion-ai (API keys never exposed to the browser).
Used for the URL-bookmarking form — task imports use addTaskToNotion instead.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
prompt |
string | User prompt text. |
|
opts |
Object |
<optional> |
Optional overrides ( |
- Source:
Throws:
-
If the API returns a non-OK status.
- Type
- Error
Returns:
The concatenated text content of the response.
- Type
- Promise.<string>
carryMigTask(task)
Duplicates the task into next month (first day) and marks original as migrated.
Parameters:
| Name | Type | Description |
|---|---|---|
task |
Object |
- Source:
checkBlockNotifications()
Checks all of today's time blocks and acts on ones that have just become active:
- Meeting blocks: auto-starts a log entry and timer at the scheduled start time.
- Task blocks: prompts the user to switch/start within a 3-minute window.
Each block is only acted on once (tracked in
notifiedBlocks). No-ops when not viewing today.
- Source:
checkChime(elapsedMs)
Fires an audible chime if the elapsed time has crossed a configured interval boundary since the last chime. No-ops when the timer is paused.
Parameters:
| Name | Type | Description |
|---|---|---|
elapsedMs |
number | Elapsed time in milliseconds. |
- Source:
checkPomoWeeklyClear()
Clears the pomodoro log when a new ISO week begins. Compares the stored week key against the current ISO week; if they differ, the log is removed and the new week key is saved.
- Source:
clampBlockSlot(slot, duration) → {number}
Keeps a block inside the day grid: one of duration slots can start no
earlier than slot 0 and no later than TB_SLOTS - duration.
Parameters:
| Name | Type | Description |
|---|---|---|
slot |
number | Requested start slot. |
duration |
number | Block length in half-hour slots. |
- Source:
Returns:
The nearest valid start slot.
- Type
- number
clearDirHandle() → {Promise.<void>}
Clears the persisted FSA directory handle from both IndexedDB and the in-memory cache so future exports fall back to browser downloads.
- Source:
Returns:
- Type
- Promise.<void>
clearTimerInterval() → {void}
Stops the running timer's tick, if one is held, and resets the stored ID to null. A no-op when nothing is ticking. Callers used to repeat the read/clear/reset sequence by hand; keeping it here means the interval can never be cleared without also being forgotten.
Returns:
- Type
- void
closeAllEditors()
Closes every open inline time-editor panel and restores the display spans.
- Source:
closeGapReportOverlay()
Hides the gap-report overlay and restores focus to its trigger.
- Source:
closeMigration()
Closes the migration overlay.
- Source:
closePlanReviewOverlay()
Hides the checklist overlay, restores focus, and marks the week reviewed.
- Source:
closeRapid()
Hides the quick-capture overlay and stops the elapsed ticker.
- Source:
closeTrackerForm() → {void}
Hides the new-tracker form and resets the open-state flag.
- Source:
Returns:
- Type
- void
closeWeeklyReportOverlay() → {void}
Hides the weekly-report overlay and restores focus to its trigger.
- Source:
Returns:
- Type
- void
collectRecentlyUsedCatIds(sources, cutoffIso) → {Set.<string>}
Collects the IDs of every epic referenced by a log entry or a board task
dated on or after cutoffIso. Records with no date are ignored rather
than assumed recent — an undated record cannot vouch for an epic's
freshness, and treating it as recent would keep stale epics alive forever.
Parameters:
| Name | Type | Description |
|---|---|---|
sources |
EpicUsageSources | Log entries and board tasks to scan. |
cutoffIso |
string | Inclusive start of the window, as YYYY-MM-DD. |
- Source:
Returns:
Epic IDs used within the window.
- Type
- Set.<string>
commitBannerNote()
Saves a timestamped note attached to the currently-active timer entry. The note is stored in logNotes (type: 'session-note') and rendered nested under its parent entry in the flow/log views, rather than as a standalone time-tracked entry. No-ops when there is no active timer or the note is empty.
- Source:
computeDayBounds(dayEntries, timedEntries, opts) → {Object}
Computes the day's start and end timestamps for the plaintext export header.
Start: the supplied day start (today only) or, failing that, the earliest entry start. End: the latest tracked end among timed entries, extended by the active timer's effective end so "Ended:" reflects work still in progress.
Pure: all environmental inputs (day start, the active timer, the current time)
are injected via opts so the function can be unit-tested without globals.
Parameters:
| Name | Type | Description | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dayEntries |
Array.<Object> | All entries for the viewed day. |
|||||||||||||||
timedEntries |
Array.<Object> | Entries with a real tracked duration ( |
|||||||||||||||
opts |
Object | Injected environment. Properties
|
- Source:
Returns:
Day bounds in ms.
- Type
- Object
config(cfg)
Logs a snapshot of runtime configuration at startup in a collapsed group. Called once by 07-lifecycle.js after state is loaded.
Parameters:
| Name | Type | Description |
|---|---|---|
cfg |
Object | Key/value pairs to display (e.g. version, entry count). |
(async) copyEodTimesheetDescription() → {Promise.<void>}
Copies the description as currently edited in the form to the clipboard, so it can be pasted into the timesheet by hand.
- Source:
Returns:
Resolves once the status message is set.
- Type
- Promise.<void>
copySummaryText(rows, weekTotalMs, locationMap) → {void}
Copies the standup text to the clipboard. Shows a brief "Copied!" confirmation on success, or logs a warning on failure.
Parameters:
| Name | Type | Description |
|---|---|---|
rows |
Array.<object> | Output of buildRollingSummary. |
weekTotalMs |
number | Sum of all days' tracked time in ms. |
locationMap |
Record.<string, string> | Pre-read location map. |
- Source:
Returns:
- Type
- void
copyWeeklyReportText() → {void}
Copies the rendered report text to the clipboard. Reads directly from the
visible <pre> block rather than rebuilding a parallel string, so the
copied text can never drift from what's on screen. Shows a brief
"Copied!" confirmation on success, or logs a warning on failure.
- Source:
Returns:
- Type
- void
createCategory(rawLabel) → {Object|null}
Creates and appends a new category from a raw label, unless a category with the same label already exists (case-insensitive). Shared by the task board's and the log entry's "+ new epic" pickers so both stay in sync (10b-tasks-events.js, 04-render.js).
Parameters:
| Name | Type | Description |
|---|---|---|
rawLabel |
string | User-entered label text, not yet trimmed. |
- Source:
Returns:
The new category, or null when the label is empty or already taken.
- Type
- Object | null
createEmojiOptionButton(emoji, onSelect) → {HTMLButtonElement}
Creates one selectable emoji button for the picker grid. The emoji is the visible label, but an explicit aria-label ("Select 😀") keeps the name consistent across screen readers, which otherwise announce emoji differently or skip them (WCAG 4.1.2).
Parameters:
| Name | Type | Description |
|---|---|---|
emoji |
string | The emoji character this button selects. |
onSelect |
function | Called when the button is clicked. |
- Source:
Returns:
The button, not yet attached to the DOM.
- Type
- HTMLButtonElement
createRestartedEntry(text, tag) → {Object}
Builds a fresh log entry that continues previously logged work on text,
used by every "restart with timer" entry point (the log's ▶ restart button,
the kanban board's ▸ track button, and the "+ track recent" chips) so they
behave consistently.
If a prior entry with the same text exists, its proof link is carried over
automatically. Its note is deliberately not copied onto the new entry
directly — a note written for an earlier session may no longer describe
this one — instead _entryMetaEditId and _pendingNoteConfirm (both in
04a-render-entry-meta.js) are set so the new entry's proof-link/note editor opens with
a "same note as last time?" prompt for the user to confirm or clear.
Parameters:
| Name | Type | Description |
|---|---|---|
text |
string | Entry text. |
tag |
string | Category id. |
- Source:
Returns:
A new entry object, not yet pushed to entries.
- Type
- Object
currentPlanReviewWeekKey() → {string}
Returns the current ISO-week key (e.g. '2026-W32'), matching the format checkPomoWeeklyClear() already uses in 07-lifecycle.js.
- Source:
Returns:
- Type
- string
cycleSignifier(entryId)
Advances an entry's signifier one step through SIG_CYCLE and persists the change. Wraps from the last value back to null (no signifier).
Parameters:
| Name | Type | Description |
|---|---|---|
entryId |
string | ID of the entry to update. |
- Source:
debug(msg, dataopt)
Emits a debug-level message, visible only when DevTools verbose level is on.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
msg |
string | Human-readable message. |
|
data |
* |
<optional> |
Optional value to attach to the log line. |
deleteSelectedEpic() → {boolean}
Deletes the currently selected epic after confirming with the user, unless it is a built-in (PROTECTED_CAT_IDS: work, meeting, focus, break, other) — those are never offered for deletion at all, since 'work' is the hardcoded startup/reset fallback and 'other' is getCat()'s own fallback for unknown tags. A selectedTag that matches no stored epic (stale after an import or a delete elsewhere) is also refused, because getCat() would silently substitute 'other' and the function would save and log a deletion that never happened.
Unlike tidyStaleEpics()/archiving, this is a hard delete: the category record itself is removed, not flagged. Any log entry or board task still tagged with this epic keeps that tag string, but getCat() can no longer resolve it and falls back to 'other' — permanently, since there is nothing left to restore. The confirm text says so explicitly and names how many entries/tasks would be affected, mirroring tidyStaleEpics()'s pattern of showing the user what is about to happen before it happens.
- Source:
Returns:
True if the epic was deleted, false if blocked, unknown or the user declined the confirm.
- Type
- boolean
dk(d) → {string}
Formats a Date as YYYY-MM-DD using local calendar date. Uses local date components (getFullYear / getMonth / getDate) so the returned string always matches the date the user sees on their clock, regardless of timezone.
Parameters:
| Name | Type | Description |
|---|---|---|
d |
Date | Date to format. |
- Source:
Returns:
e.g. '2026-05-26'
- Type
- string
Example
dk(new Date(2026, 4, 26, 12, 0, 0)) // → '2026-05-26' (noon local)
dk(new Date(2026, 11, 31, 23, 59, 0)) // → '2026-12-31' (11 PM local)
dk(new Date(2026, 0, 1, 0, 0, 0)) // → '2026-01-01' (midnight local)
drawPomoSegments()
Redraws all pomodoro SVG segments to reflect the current remaining time. Each minute is a sector; filled sectors fade as time elapses.
- Source:
dropMigTask(task)
Archives (drops) the task by marking it done and migrated.
Parameters:
| Name | Type | Description |
|---|---|---|
task |
Object |
- Source:
durLabel(tsStart, tsEnd) → {string}
Builds an HTML <span class="etime-dur"> containing the human-readable
duration between two timestamps. Returns an empty string if the duration
is zero or negative.
Parameters:
| Name | Type | Description |
|---|---|---|
tsStart |
number | Start Unix timestamp (ms). |
tsEnd |
number | End Unix timestamp (ms). |
- Source:
Returns:
HTML string, or '' if duration ≤ 0.
- Type
- string
ensureDayStarted() → {void}
Records start-of-day silently (no backup-restore dialog) if the day has not already been started. Called automatically when the first task timer begins.
- Source:
Returns:
- Type
- void
enterEmergency()
Activates focus/emergency mode: hides the main UI, shows the overlay, moves the pomodoro section into the overlay, and focuses the next-action input. The previously saved next-action note is restored if one exists for the active entry.
- Source:
eodKey(dayopt) → {string}
Returns the localStorage key for a given day's end-of-day timestamp.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
day |
Date |
<optional> |
viewDate | Day to key by; defaults to the day in view. |
- Source:
Returns:
Key in the format wl_eod_YYYY-MM-DD.
- Type
- string
epicCutoffDate(todayIso, windowDays) → {string}
Returns the ISO date (YYYY-MM-DD) that starts an inactivity window ending
today. The window is inclusive of both ends: with windowDays of 21 and a
todayIso of 2026-08-21, the cutoff is 2026-08-01 and an epic last used on
exactly that date still counts as active.
Parameters:
| Name | Type | Description |
|---|---|---|
todayIso |
string | Today's date as YYYY-MM-DD. |
windowDays |
number | Length of the activity window in days. |
- Source:
Returns:
The inclusive cutoff date as YYYY-MM-DD.
- Type
- string
Example
epicCutoffDate('2026-08-21', 21) // → '2026-08-01'
error(msg, dataopt)
Emits an error — something that broke or produced an incorrect result.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
msg |
string | Human-readable message. |
|
data |
* |
<optional> |
Optional value to attach to the log line. |
escHtml(s) → {string}
Escapes a string for safe insertion as HTML text content.
Parameters:
| Name | Type | Description |
|---|---|---|
s |
string | Raw string to escape. |
- Source:
Returns:
HTML-escaped string safe for insertion into the DOM.
- Type
- string
Example
escHtml('<b>bold</b>') // → '<b>bold</b>'
escHtml('a & b') // → 'a & b'
escHtml('"quoted"') // → '"quoted"'
escHtml(42) // → '42'
exitEmergency()
Deactivates focus/emergency mode: restores the main UI, saves the next-action note, moves the pomodoro section back to its normal position, and auto-expands the active task's checkpoint list so the user can pick up where they left off.
- Source:
exportBackup()
Exports a JSON backup of recent application state: entries, plan tasks, time blocks, dev log, and distractions from the last BACKUP_RETENTION_DAYS days, plus the (undated) categories and hidden quick-pick items and the source-capped pomodoro log. Every time-series array is trimmed to the retention window so the file size stays bounded; older records remain in earlier backup files. Triggers a file download or writes to the save folder.
- Source:
exportTxt()
Exports the currently viewed day's log as a plaintext file. Groups entries by category and task, includes a header with day start/end times, tracked/billable/internal totals, and the implied (untracked) break time; lists each task's individual tracked sessions as time ranges plus any plan-task/entry notes and proof links as indented lines; finishes with a pasteable per-item summary line and, when anything looks off (undocumented work, an unbroken >4h block, a full day with no break-sized gap), a warnings section. Writes to the user's chosen save folder via the File System Access API, or falls back to a browser download.
- Source:
(async) fetchAndRenderCalendar() → {Promise.<void>}
Fetches today's meetings from the local PowerShell proxy (/api/calendar),
caches the result, filters out user-hidden meetings, and calls
renderCalStrip. Logs a warning and shows a fallback message on error.
- Source:
Returns:
- Type
- Promise.<void>
fetchCalendarEvents()
Fetches today's Finnish flag days, public holidays, and notable days from the
Nimipäivärajapinta Typesense API, displaying the first match in #liveFlagDay.
If today has no event, shows the next upcoming one. Falls back to the hardcoded
FLAG_DAYS_FIXED list if the API is unreachable.
- Source:
fetchNameday()
Fetches today's Finnish and Swedish name days from the Nimipäivärajapinta API
and renders them in #liveNameday. Falls back to an "API unavailable" note
on error.
- Source:
fetchWeather()
Fetches current weather, hourly precipitation probability, and sunrise/sunset
data from the Open-Meteo API for the configured location (WEATHER_LAT,
WEATHER_LON) and populates #liveWeather, #liveRain, and
#liveSunrise. No-ops gracefully on file: protocol or network error.
- Source:
filterNewBackupEntries(currentEntries, backupEntries, isValid) → {Array.<Object>}
Returns entries from backupEntries that are both schema-valid and absent
from currentEntries (compared by id). Pure: no side-effects, no browser
APIs.
Separated from the import flow so the deduplication logic can be
unit-tested independently of localStorage and the File API.
mergeBackupEntries() calls this before writing to localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
currentEntries |
Array.<Object> | Entries already in the local store. |
backupEntries |
Array.<Object> | Entries from the backup file. |
isValid |
function | Entry-schema predicate (e.g. |
- Source:
Returns:
Valid entries from backupEntries whose id does
not appear in currentEntries.
- Type
- Array.<Object>
findExportWarnings(dayEntries, opts) → {Array.<string>}
Flags anomalies in a day's entries that would otherwise only surface when
someone questions the timesheet later: work logged with no way to check it,
suspiciously long unbroken stretches, and a full workday with no break-sized
gap in it. Surfaced as a warnings section at the end of the plaintext export
so the person doing the logging finds the gap before anyone asking about it
does. Non-billable entries are never flagged for a missing note/link — see
findGapReportEntries for why the _billable flag must already be
resolved on each entry before it reaches this function.
Pure: the duration formatter is injected (same convention as formatGroupedLines) so this has no dependency on global state.
Parameters:
| Name | Type | Description | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
dayEntries |
Array.<Object> | All entries for the exported day, each
optionally carrying a resolved |
||||||||||||||||||||
opts |
Object |
Properties
|
- Source:
Returns:
Human-readable warning lines; empty when nothing stands out.
- Type
- Array.<string>
Example
findExportWarnings(
[{ text: 'Fix login', ts: 0, tsEnd: 3600000, signifier: '' }],
{ workdaySpanMs: 3600000, untrackedMs: 0, fmtDuration: (ms) => `${ms}ms` }
)
// → ['No note or link: Fix login']
findGapReportEntries(entries, weekStart, weekEnd) → {Array.<Object>}
Finds finished, non-cancelled, billable work entries within
[weekStart, weekEnd) that have neither a proof link nor a note —
candidates for the end-of-week gap report. Uses the same "finished and
not cancelled" filter as every other report/aggregation in this codebase
(buildRollingSummary, findLargestGap in 11-timeflow.js,
exportTxt's billable summary), plus excludes break/lunch/meeting
utility entries which never need documentation.
Non-billable entries are never flagged: this is a pure module with no
access to the category/task lookups isEntryBillable needs, so
callers resolve billable status themselves and annotate each entry with
_billable before calling this (see exportTxt's
entriesWithBillingStatus for the same convention). An entry with no
_billable property is treated as billable, matching
isEntryBillable's own "undefined means billable" default.
Parameters:
| Name | Type | Description |
|---|---|---|
entries |
Array.<Object> | All log entries, each optionally carrying
a resolved |
weekStart |
number | Inclusive week-start timestamp in ms (e.g. Monday 00:00). |
weekEnd |
number | Exclusive week-end timestamp in ms (e.g. the following Monday 00:00). |
- Source:
Returns:
Matching entries, sorted by ts ascending.
- Type
- Array.<Object>
Example
findGapReportEntries(
[{ id: '1', text: 'Fix login', ts: 100, tsEnd: 200, date: '2026-06-01' }],
0, 1000
)
// → [{ id: '1', text: 'Fix login', ts: 100, tsEnd: 200, date: '2026-06-01' }]
findLargestGap(dateKey) → {Object|null}
Finds the largest untracked gap (≥ 15 min) today, including gaps between consecutive completed entries AND the trailing gap from the most recent entry's end to now (which is often the most actionable). Returns null for past days — only actionable today.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | YYYY-MM-DD. |
- Source:
Returns:
- Type
- Object | null
findMostRecentEntryForText(text) → {Object|undefined}
Finds the most recently created log entry with the same text (case-
insensitive, trimmed) as text. Used to carry proof-link/note context
forward when a task or entry is restarted with a fresh timer.
Parameters:
| Name | Type | Description |
|---|---|---|
text |
string | The entry/task text to match against. |
- Source:
Returns:
The most recent matching entry, or undefined if none exists.
- Type
- Object | undefined
findPromotableTask(planTasks, text, todayKey) → {Object|null}
Finds today's plan task matching text (case-insensitive), for promotion
to "in progress" when a timer starts on that text — regardless of which UI
control (hero composer, recent chip, capture input, board's own "track"
button) started it. Returns null when there's no match, or the match
isn't in a promotable state (only todo/upcoming tasks are promoted —
an already-inprogress, pending, blocked, or done task is left
alone). Doesn't mutate planTasks; callers apply the status change.
Task/entry linkage is by date + case-insensitive text match — the same
convention buildTaskNoteMap uses, because plan tasks and log
entries share no taskId field.
Parameters:
| Name | Type | Description |
|---|---|---|
planTasks |
Array.<Object> | All plan/board tasks. |
text |
string | The entry text a timer was just started on. |
todayKey |
string | Today's date key (YYYY-MM-DD). |
- Source:
Returns:
The matching plan task to promote, or null.
- Type
- Object | null
Examples
findPromotableTask(
[{ id: 't1', text: 'Fix login', date: '2026-06-04', status: 'todo' }],
'Fix login',
'2026-06-04'
) // → { id: 't1', text: 'Fix login', date: '2026-06-04', status: 'todo' }
findPromotableTask(
[{ id: 't1', text: 'Fix login', date: '2026-06-04', status: 'inprogress' }],
'Fix login',
'2026-06-04'
) // → null — already in progress, nothing to promote
findStaleCategories(params) → {Object}
Works out which epics have had no recorded activity inside the window and are therefore safe to archive out of the pickers.
An epic is kept when any of these hold: it is a built-in (PROTECTED_CAT_IDS), it is the epic currently selected in the tag row, it is already archived, or a log entry or board task dated within the window references it.
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
params |
Object | Inputs for the staleness decision. Properties
|
- Source:
Returns:
IDs to archive (in categories order) and the cutoff date used.
- Type
- Object
findTimesheetEntryProblem(entry) → {string|null}
Checks a timesheet entry before it is sent anywhere. Shared by the browser form and scripts/lib/eod-automation.mjs so both reject the same inputs.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | Candidate entry with |
- Source:
Returns:
A message naming the first problem, or null when valid.
- Type
- string | null
findWeeklyPlanReviewTasks(planTasks, weekStartKey, weekEndKey) → {Array.<Object>}
Finds plan tasks explicitly scheduled as 'upcoming' whose date falls within
[weekStartKey, weekEndKey) — candidates for the weekly plan review
checklist, run once a new ISO week begins so stale "upcoming" tasks (ones
already finished elsewhere before their target week arrived) get caught
and reconciled instead of silently resurfacing on their date.
Compares date as strings, not timestamps: YYYY-MM-DD keys already sort
and compare correctly lexicographically, so no Date conversion is needed.
Parameters:
| Name | Type | Description |
|---|---|---|
planTasks |
Array.<Object> | All plan/board tasks. |
weekStartKey |
string | Inclusive YYYY-MM-DD (e.g. this week's Monday). |
weekEndKey |
string | Exclusive YYYY-MM-DD (e.g. next week's Monday). |
- Source:
Returns:
Matching tasks, sorted by date ascending.
- Type
- Array.<Object>
Example
findWeeklyPlanReviewTasks(
[{ id: '1', text: 'PROJ-1: Fix login', status: 'upcoming', date: '2026-06-03' }],
'2026-06-01', '2026-06-08'
)
// → [{ id: '1', text: 'PROJ-1: Fix login', status: 'upcoming', date: '2026-06-03' }]
flashCopiedLabel(button, durationMsopt) → {void}
Briefly replaces a copy button's label with "Copied!", then restores the original markup (emoji span included). The original is remembered on first use, not read at each click: a second click inside the flash would otherwise capture "Copied!" as the label to restore, leaving the button stuck.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
button |
HTMLElement | The copy button to flash. |
||
durationMs |
number |
<optional> |
2000 | How long "Copied!" stays visible. |
- Source:
Returns:
- Type
- void
flatSort(tasks) → {Array.<Object>}
Sorts a flat list of plan tasks by status order and priority, inserting child tasks immediately after their parent. Tasks matching the currently running timer text are sorted first. Orphaned children (whose parent has been deleted or moved) are appended at the end.
Parameters:
| Name | Type | Description |
|---|---|---|
tasks |
Array.<Object> | Array of plan task objects to sort. |
- Source:
Returns:
New sorted array with children interleaved after parents.
- Type
- Array.<Object>
fmtAgo(ts, nowopt) → {string}
Formats a past timestamp as a relative "ago" string for the last-note reference line.
Uses an injected now parameter so it is fully unit-testable without mocking Date.now.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
ts |
number | Unix timestamp in milliseconds of the past event. |
|
now |
number |
<optional> |
Current time in ms; defaults to Date.now(). |
- Source:
Returns:
'just now' | 'X min ago' | 'Xh ago'
- Type
- string
Example
fmtAgo(Date.now() - 30000) // → 'just now'
fmtAgo(Date.now() - 2 * 60000) // → '2 min ago'
fmtAgo(Date.now() - 90 * 60000) // → '1h ago'
fmtDateLabel(dateKey) → {string}
Formats a YYYY-MM-DD date key as a short day-of-week + date label. e.g. '2026-06-04' → 'Wed 04 Jun'
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string |
- Source:
Returns:
- Type
- string
fmtDur(ms) → {string}
Formats a duration in milliseconds as a compact human-readable string ("Xh Ym"). Used throughout the UI for tracked-time display in the timeline, chart, and plan.
Parameters:
| Name | Type | Description |
|---|---|---|
ms |
number | Duration in milliseconds. |
- Source:
Returns:
e.g. '1h 30m', '45m', '2h'
- Type
- string
Example
fmtDur(0) // → '0m'
fmtDur(45 * 60 * 1000) // → '45m'
fmtDur(90 * 60 * 1000) // → '1h 30m'
fmtDur(120 * 60 * 1000) // → '2h'
fmtDurLong(ms) → {string}
Formats a duration in milliseconds as a human-readable string using the long "min" suffix. Used in plaintext exports where readability matters more than compactness.
Parameters:
| Name | Type | Description |
|---|---|---|
ms |
number | Duration in milliseconds. |
- Source:
Returns:
e.g. '1h 30min', '45min', '2h'
- Type
- string
Example
fmtDurLong(0) // → '0min'
fmtDurLong(45 * 60 * 1000) // → '45min'
fmtDurLong(90 * 60 * 1000) // → '1h 30min'
fmtDurLong(120 * 60 * 1000) // → '2h'
fmtElapsed(ms) → {string}
Formats a duration in milliseconds as a compact time string.
Parameters:
| Name | Type | Description |
|---|---|---|
ms |
number | Duration in milliseconds. |
- Source:
Returns:
'MM:SS' for durations under an hour; 'HH:MM:SS' otherwise.
- Type
- string
Example
fmtElapsed(0) // → '00:00'
fmtElapsed(90 * 1000) // → '01:30'
fmtElapsed(3600 * 1000) // → '01:00:00'
fmtElapsed(5461 * 1000) // → '01:31:01'
fmtHm(ts) → {string}
Formats a Unix timestamp (ms) as HH:MM in local time.
Parameters:
| Name | Type | Description |
|---|---|---|
ts |
number |
- Source:
Returns:
- Type
- string
fmtLabel(d) → {string}
Returns a human-readable day label: 'today', 'yesterday', or a short locale date string.
Parameters:
| Name | Type | Description |
|---|---|---|
d |
Date | The date to label. |
- Source:
Returns:
'today', 'yesterday', or a short locale date such as 'Mon, Jan 5'.
- Type
- string
fmtMD(d) → {string}
Formats a Date as a zero-padded "MM-DD" string used as flag-day map keys.
Parameters:
| Name | Type | Description |
|---|---|---|
d |
Date | The date to format. |
- Source:
Returns:
e.g. "06-04" for the 4th of June.
- Type
- string
fmtTime(ts) → {string}
Formats a Unix timestamp as HH:MM in 24-hour local time.
Parameters:
| Name | Type | Description |
|---|---|---|
ts |
number | Unix timestamp in milliseconds. |
- Source:
Returns:
e.g. '09:30'
- Type
- string
Example
fmtTime(new Date('2026-05-25T09:05:00').getTime()) // → '09:05'
fmtTime(new Date('2026-05-25T14:30:00').getTime()) // → '14:30'
focusBlockEmojiButton(bid) → {void}
Focuses the emoji trigger button for a planned block after the grid is
re-rendered by renderTimeblock(). Searches by data-bid rather than
building a CSS selector from untrusted data (avoids injection risk).
Parameters:
| Name | Type | Description |
|---|---|---|
bid |
string | Block ID. |
- Source:
Returns:
- Type
- void
focusPlannedBlock(blockId) → {void}
Moves keyboard focus to a planned block's element, found by ID after the grid has been redrawn. Compares IDs in code instead of building a selector, because block IDs come from stored data.
Parameters:
| Name | Type | Description |
|---|---|---|
blockId |
string | ID of the block to focus. |
- Source:
Returns:
- Type
- void
focusTabAt(nextIndex)
Selects the view at index nextIndex in TF_VIEWS, focuses its tab button,
and re-renders. Shared by the keyboard handler in initTodayFlow().
Parameters:
| Name | Type | Description |
|---|---|---|
nextIndex |
number |
- Source:
formatGroupedLines(catOrder, catGrouped, fmtDuration, getCatLabel, taskNotesopt, taskLinksopt, fmtSessionRangeopt) → {Array.<string>}
Renders the grouped-by-category structure into indented text lines: one line per category (with its total), each followed by its indented task lines and, when the task carries tracked sessions, a note, and/or a proof link, further indented sub-lines for each — session time ranges first, then note lines, then a link line — so a task worked in two separate sessions still shows both time ranges instead of only their collapsed total.
Pure: the duration/time formatters and category-label resolver are injected so this function has no dependency on global state and can be unit-tested directly.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
catOrder |
Array.<string> | Category keys in display order. |
|
catGrouped |
Object | Grouping produced by groupEntriesByCategory. |
|
fmtDuration |
function | Formats a duration in ms (e.g. |
|
getCatLabel |
function | Resolves a category key to its label. |
|
taskNotes |
Object.<string, string> |
<optional> |
Map of lowercased task text to note, as produced by buildTaskNoteMap. Omitted tasks render with no note line. |
taskLinks |
Object.<string, string> |
<optional> |
Map of lowercased task text to a comma-separated link list, as produced by buildEntryLinkMap. Omitted tasks render with no link line. |
fmtSessionRange |
function |
<optional> |
Formats
one tracked session as a time-range string (e.g. |
- Source:
Returns:
The body lines for the export file.
- Type
- Array.<string>
formatWeeklyTicketSummaryText(ticketOrder, grouped, fmtDuration) → {Array.<string>}
Renders the structure produced by buildWeeklyTicketSummary into
text lines: one header line per ticket (with its total), each followed by
an indented bullet per distinct task name (with its own subtotal) and any
collected note:/link: lines — same indentation convention as
formatGroupedLines in pure-fns-gapreport.js. Ticket blocks are
separated by a blank line.
Pure: the duration formatter is injected so this has no dependency on global state and can be unit-tested directly.
Parameters:
| Name | Type | Description |
|---|---|---|
ticketOrder |
Array.<string> | Ticket keys in display order. |
grouped |
Object | Grouping produced by buildWeeklyTicketSummary. |
fmtDuration |
function | Formats a duration in ms (e.g. |
- Source:
Returns:
The report body lines; empty when ticketOrder is empty.
- Type
- Array.<string>
getActiveTimer() → {object|null}
Returns the current active timer object, or null when no timer is running.
Returns:
The active timer state object, or null.
- Type
- object | null
getBlocks() → {Array}
Returns the live time-blocks array.
Returns:
The blocks.
- Type
- Array
getCat(id) → {Object}
Returns the category object for id, falling back to the built-in 'other'
category or, when that is also absent, a hardcoded stub. The returned colour
always passes through safeCssColor() to strip any unsafe CSS value.
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string | Category ID. |
- Source:
Returns:
- Type
- Object
getCatColor(id) → {string}
Returns the CSS colour string for the category with the given ID.
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string | Category ID. |
- Source:
Returns:
CSS colour value, always a safe CSS string.
- Type
- string
getCatLabel(id) → {string}
Returns the display label for the category with the given ID.
Parameters:
| Name | Type | Description |
|---|---|---|
id |
string | Category ID. |
- Source:
Returns:
Category label.
- Type
- string
getCategories() → {Array.<{id: string, label: string, color: string}>}
Returns the live categories array.
Returns:
Categories array.
- Type
- Array.<{id: string, label: string, color: string}>
getDayStart(dayopt) → {number|null}
Retrieves a given day's recorded start-of-day timestamp from localStorage.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
day |
Date |
<optional> |
viewDate | Day to read; defaults to the day in view. |
- Source:
Returns:
Unix timestamp (ms), or null if not yet set.
- Type
- number | null
getElapsedMs() → {number}
Returns the total elapsed milliseconds for the active timer. Accounts for accumulated time from previous pause/resume cycles. Returns 0 if no timer is active.
- Source:
Returns:
Elapsed time in milliseconds.
- Type
- number
getEntries() → {Array}
Returns the current array of work-log entries.
Returns:
The live entries array.
- Type
- Array
getEodTs(dayopt) → {number|null}
Retrieves a given day's recorded end-of-day timestamp from localStorage.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
day |
Date |
<optional> |
viewDate | Day to read; defaults to the day in view. |
- Source:
Returns:
Unix timestamp (ms), or null if not yet set.
- Type
- number | null
getFlagDays(year) → {Object.<string, string>}
Returns an object mapping "MM-DD" strings to Finnish flag-day names for the given year. Combines fixed dates with computed moveable feasts.
Parameters:
| Name | Type | Description |
|---|---|---|
year |
number | The full 4-digit year. |
- Source:
Returns:
Map of "MM-DD" → event name.
- Type
- Object.<string, string>
getFlowView() → {'flow'|'log'|'blocks'|'month'|'summary'}
Returns the persisted view preference, defaulting to 'flow'.
- Source:
Returns:
- Type
- 'flow' | 'log' | 'blocks' | 'month' | 'summary'
getISOWeek(d) → {number}
Returns the ISO 8601 week number for a given date.
Parameters:
| Name | Type | Description |
|---|---|---|
d |
Date | The date to evaluate. |
- Source:
Returns:
ISO week number (1–53).
- Type
- number
getIterationExpiry(completedDay) → {string|null}
Returns the first iteration expiry date that is strictly later than
completedDay, or null if none is configured beyond that date.
Parameters:
| Name | Type | Description |
|---|---|---|
completedDay |
string | Completion date in "YYYY-MM-DD" format. |
- Source:
Returns:
The next expiry date, or null.
- Type
- string | null
getLogNotes() → {Array}
Returns the live log-notes array.
Returns:
The log notes.
- Type
- Array
getMigrationRecord() → {Object}
Returns the migration completion record (map of YYYY-MM → boolean).
- Source:
Returns:
- Type
- Object
getMoonData(date) → {Object}
Calculates moon phase, illumination percentage, and zodiac sign for a date using a simplified version of the Meeus algorithm.
Parameters:
| Name | Type | Description |
|---|---|---|
date |
Date | The date to evaluate. |
- Source:
Returns:
emoji = phase emoji, phase = phase name, illum = illumination (%),
sign = [symbol, name] of the current zodiac sign.
- Type
- Object
getPlanTasks() → {Array}
Returns the live plan-tasks array.
Returns:
The plan tasks.
- Type
- Array
getSavedDir() → {Promise.<(FileSystemDirectoryHandle|null)>}
Retrieves the previously granted File System Access directory handle from IndexedDB (with an in-memory cache).
- Source:
Returns:
The handle, or null if none saved.
- Type
- Promise.<(FileSystemDirectoryHandle|null)>
getSelectedTag() → {string}
Returns the currently selected tag (e.g. 'work', 'personal').
Returns:
The selected tag.
- Type
- string
getTimerInterval() → {number|null}
Returns the current setInterval ID for the running timer, or null.
Returns:
The interval ID, or null when not ticking.
- Type
- number | null
getTrackers() → {Array}
Returns the live trackers array.
Returns:
The trackers.
- Type
- Array
getViewDate() → {Date}
Returns the Date the timeline is currently showing.
Returns:
The view date.
- Type
- Date
getViewLocation() → {string}
Resolves the work location for the currently viewed day.
- Source:
Returns:
A location id present in WORK_LOCATIONS.
- Type
- string
groupEntriesByCategory(dayEntries) → {Object}
Groups a day's log entries by category and, within each category, by task
(case-insensitively), preserving first-seen order. Accumulates tracked time
(where tsEnd > ts) per task and per category.
Pure data transform — reads only entry fields and performs no formatting, so the caller decides how to render the durations and labels.
Parameters:
| Name | Type | Description |
|---|---|---|
dayEntries |
Array.<Object> | Entries for the viewed day. |
- Source:
Returns:
catOrder is the list of
category keys in first-seen order; catGrouped[catKey] is
{ totalMs, tasks: { [taskKey]: { label, totalMs, hasTime, sessions } }, taskOrder }.
sessions is the task's individual tracked {ts, tsEnd} pairs, in
encounter order — kept separate (not merged) so a task worked in two
sessions still shows both time ranges rather than one collapsed total.
- Type
- Object
groupTasksByColumn(viewKey) → {Object}
Partitions today's plan tasks into the three kanban column groups.
Parameters:
| Name | Type | Description |
|---|---|---|
viewKey |
string | Date key (YYYY-MM-DD) for the current view. |
- Source:
Returns:
- Type
- Object
handleBlockKeydown(event, block, dateKey) → {void}
Keydown handler for a planned block: ArrowUp / ArrowDown move it one half-hour slot through moveBlockToSlot(), the same path drag-and-drop uses, and focus follows the block across the redraw. Keys pressed on the block's own buttons and any Ctrl/Alt/Meta/Shift combination are left alone so those controls and assistive-technology shortcuts keep working.
Parameters:
| Name | Type | Description |
|---|---|---|
event |
KeyboardEvent | The keydown event, listened for on the block element. |
block |
Object | The block the listener belongs to. |
dateKey |
string | YYYY-MM-DD of the day in view. |
- Source:
Returns:
- Type
- void
handleDistractClick()
Handles the "distracted" button: pauses a running timer, asks for an optional note, and logs the distraction. Cancelling the prompt logs nothing and resumes the timer — but only if this click paused it, so a timer the user had already paused stays paused (#607).
- Source:
handleMoodSelect(mood, icon)
Handles a mood selection from the banner dropdown. Records a distraction or parked thought as an entry (for distracted/parked moods), triggers the hook panel for 'interesting', and sets focus mode for 'focus'. Closes the panel afterwards.
Parameters:
| Name | Type | Description |
|---|---|---|
mood |
string | One of 'distracted' | 'parked' | 'focus' | 'interesting'. |
icon |
string | Emoji icon for the mood. |
- Source:
heroEnterStopped(entry)
Called by stopTimer() just before activeTimer is cleared. Switches the hero to the stopped state and arms the 6s auto-dismiss. It does not render: stopTimer() calls render() right afterwards, which re-renders the hero card, so rendering here as well would draw the same state twice.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | The log entry that was just stopped. |
- Source:
heroGetState() → {'idle'|'running'|'paused'|'stopped'}
Derives the current hero state from module-level timer variables.
- Source:
Returns:
- Type
- 'idle' | 'running' | 'paused' | 'stopped'
heroUpdateClock()
Updates the running-state elapsed clock without a full render. Only touches the clock element so the DOM churn stays minimal.
- Source:
hideHandoffInput()
Hides and clears the handoff-note text field.
- Source:
hideMeetingForDay(dayKey, meeting) → {void}
Hides one meeting for the rest of the day.
Stores the occurrence key rather than the subject, so hiding one instance of a recurring meeting leaves that day's other instances on the strip.
Parameters:
| Name | Type | Description |
|---|---|---|
dayKey |
string | Day key from |
meeting |
Object | The meeting to hide. |
- Source:
Returns:
- Type
- void
hideSaveFailureBanner()
Removes the save-failure banner, if one is currently shown.
- Source:
(async) importBackup(file) → {Promise.<void>}
Restores application state from a JSON backup file previously created by exportBackup. Reads the file, validates its structure via validateBackupFile, asks the user to confirm, writes all arrays to their localStorage keys, then reloads the page so state is re-initialised cleanly from the restored data.
Assumption: import is a full replace, not a merge. All data currently in the affected localStorage keys is overwritten after the user confirms. If selective-date merging is ever needed, add a merge mode option here and update the confirmation dialog accordingly.
Parameters:
| Name | Type | Description |
|---|---|---|
file |
File | The .json backup file selected by the user. |
- Source:
Returns:
- Type
- Promise.<void>
info(msg, dataopt)
Emits an informational message.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
msg |
string | Human-readable message. |
|
data |
* |
<optional> |
Optional value to attach to the log line. |
initBannerControls()
Binds all interactive controls on the V5 timer banner:
mood dropdown, quick-note input, and Break / Lunch / Meeting utility pills.
Called once from 07-lifecycle.js after DOMContentLoaded is guaranteed.
- Source:
initBoardColumnDnD() → {void}
Registers dragover, dragleave, and drop listeners on the three static board
column lists. Called exactly once on DOMContentLoaded from 07-lifecycle.js.
Card draggable wiring (re-rendered each cycle) stays in bindBoardColumnDnD().
- Source:
Returns:
- Type
- void
initBoardTabs() → {void}
Initialises the tabbed board: wires tab-click handlers, restores the last
active tab from localStorage, and sets up drag-over-tab lane switching so
users can drag a card onto a tab label to reveal that column before dropping.
Called once on DOMContentLoaded from 07-lifecycle.js.
- Source:
Returns:
- Type
- void
initHero()
Binds all Hero Card button events. Called once from DOMContentLoaded in 07-lifecycle.js.
- Source:
initLocation() → {void}
Binds the location toggle button. Called exactly once from the boot sequence in 07-lifecycle.js; there is no re-invocation path, so the click listener is attached without a guard. If a future caller invokes this more than once, add a removeEventListener (or an "already bound" flag) first to avoid duplicate handlers. Safe to call when the button is missing.
- Source:
Returns:
- Type
- void
initMigration()
Initialises the migration overlay: wires close button and optionally shows a last-day-of-month banner once per month.
- Source:
initPomo(mins)
Initialises (or re-initialises) the pomodoro timer for the given duration. Clears any running interval, resets state, and highlights the matching duration button.
Parameters:
| Name | Type | Description |
|---|---|---|
mins |
number | Session duration in minutes. |
- Source:
initRapid()
Registers all event listeners for the quick-capture overlay. Called once on DOMContentLoaded.
- Source:
initSprints() → {void}
Registers all sprint UI event listeners: the sprint mode button, start, cancel, and Enter-key shortcut on the intention input. Called once on DOMContentLoaded.
- Source:
Returns:
- Type
- void
initTodayFlow()
Binds the static listeners exactly once on DOMContentLoaded:
- Log-note input/button (static HTML, never recreated)
- Segmented-control click + keyboard nav, delegated off the stable #tfHeader container so we survive its innerHTML being rewritten by every renderFlowHeader() call. Avoids the listener-accumulation bug that occurred when binding happened inside render functions.
- Source:
initTrackers() → {void}
Bootstraps the Trackers feature: performs the initial render and wires up the "+ New" button to toggle the tracker form open/closed. Called once on DOMContentLoaded.
- Source:
Returns:
- Type
- void
initWeeklyReport() → {void}
Binds the weekly-report modal buttons. Called exactly once from the boot sequence in 07-lifecycle.js. Safe to call when any button is missing.
- Source:
Returns:
- Type
- void
isEntryBillable(entry) → {boolean}
Determines whether a log entry is billable, using a three-tier lookup:
- The entry's own
billableflag (if explicitly set). - The matching plan task's
billableflag (matched case-insensitively by trimmed text). - The category default.
Assumption: entries and tasks where billable is undefined are treated as
billable by default. This preserves backward compatibility with data created
before the billable flag was introduced — older entries must not silently
disappear from billing reports after an upgrade.
If the default should change to non-billable, a migration of existing
localStorage data is required (see DATA.md § wl_entries).
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | Log entry object. |
- Source:
Returns:
True if the entry should be counted as billable.
- Type
- boolean
Example
isEntryBillable({ text: 'Write report', tag: 'work' }) // → true (category default)
isEntryBillable({ text: 'Write report', tag: 'work', billable: false }) // → false (explicit flag)
isEntryBillable({ text: 'Write report', signifier: 'cancelled' }) // → false (cancelled)
isLiveMeetingBlock(block, liveEntry) → {boolean}
Whether a planned meeting block is the one the running timer is tracking. Meeting entries are logged with a leading "📅 ", so the entry's text is compared both with and without that prefix.
Parameters:
| Name | Type | Description |
|---|---|---|
block |
Object | The planned block. |
liveEntry |
Object | null | undefined | The log entry the timer runs on, if any. |
- Source:
Returns:
True when block is a meeting that matches liveEntry.
- Type
- boolean
isLongRunningTimer(elapsedMs, thresholdMinsopt) → {boolean}
Returns true if a timer's elapsed time has crossed a long-running safety threshold. Pure boundary check, extracted so the threshold logic is unit-testable without mocking the DOM or the timer interval.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
elapsedMs |
number | Elapsed timer time in milliseconds. |
||
thresholdMins |
number |
<optional> |
240 | Threshold in minutes (default 4h). |
- Source:
Returns:
True once elapsedMs exceeds the threshold.
- Type
- boolean
Example
isLongRunningTimer(3 * 60 * 60 * 1000) // → false (3h)
isLongRunningTimer(4 * 60 * 60 * 1000) // → false (exactly 4h)
isLongRunningTimer(4 * 60 * 60 * 1000 + 1) // → true (just past 4h)
isLongRunningTimer(31 * 60 * 1000, 30) // → true (custom 30 min threshold)
isMeetingHidden(meeting, hiddenKeys) → {boolean}
Returns true if the user has hidden this meeting for the day.
Hidden meetings are stored per day as occurrence keys. Entries saved before that were bare subjects, which hid every meeting sharing a title — the whole day's standups would vanish on hiding one. Those legacy entries are still honoured so a meeting hidden earlier today stays hidden; the store is keyed by date, so they clear on their own overnight.
Parameters:
| Name | Type | Description |
|---|---|---|
meeting |
* | Meeting object from the calendar API response. |
hiddenKeys |
Array.<string> | Stored keys for the day. |
- Source:
Returns:
True if this meeting should stay off the strip.
- Type
- boolean
Example
isMeetingHidden({ subject: 'Standup', start: '09:00' }, ['Standup|09:00']) // → true
isMeetingHidden({ subject: 'Standup', start: '14:00' }, ['Standup|09:00']) // → false
isToday(d) → {boolean}
Returns true if d falls on today's calendar date.
Parameters:
| Name | Type | Description |
|---|---|---|
d |
Date | The date to test; only its local calendar day is compared. |
- Source:
Returns:
True when d is on the same calendar day as now.
- Type
- boolean
isWithinRetentionWindow(date, cutoffDate) → {boolean}
Checks whether a YYYY-MM-DD date string is on or after the cutoff date —
the predicate applyBackupRetention() filters entries by. Extracted as its
own function so the well-formed-date check and the chronological
comparison are named and testable independently of the filtering loop.
Parameters:
| Name | Type | Description |
|---|---|---|
date |
string | undefined | Candidate entry's |
cutoffDate |
string | Cutoff as |
- Source:
Returns:
True when date is well-formed and not before cutoffDate.
- Type
- boolean
isWorkdayLikelyOver(opts) → {boolean}
Returns true if today's workday looks like it may be over and hasn't been
exported yet: the day was started, has at least one entry, hasn't already
been ended, and workdayHours have passed since it started. Used to show
a reminder nudging the user toward "end the day" (which triggers the real
export) — this function only decides whether to nudge, it never exports
or ends the day itself.
Pure: all environmental inputs (day-start/day-end timestamps, whether
today has entries, the current time) are injected via opts so the
function can be unit-tested without touching localStorage or Date.now(),
matching computeDayBounds's style.
Parameters:
| Name | Type | Description | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opts |
Object |
Properties
|
- Source:
Returns:
True if the reminder should be shown.
- Type
- boolean
Example
isWorkdayLikelyOver({ sodTs: 1000, eodTs: null, hasEntriesToday: true, now: 1000 + 8 * 3600000 })
// → true (exactly 8h after day-start)
isWorkdayLikelyOver({ sodTs: 1000, eodTs: null, hasEntriesToday: true, now: 1000 + 7 * 3600000 })
// → false (only 7h in)
isWorkdayLikelyOver({ sodTs: null, eodTs: null, hasEntriesToday: true, now: 999999 })
// → false (day never started)
jiraKeyLinkHtml(baseUrl, key) → {string}
Builds the anchor HTML for a Jira ticket key. The link opens in a new tab, so a visually hidden suffix tells screen-reader users that before they activate it (WCAG 3.2.2 / 2.4.4); sighted users see no change.
Parameters:
| Name | Type | Description |
|---|---|---|
baseUrl |
string | Jira browse URL, without a trailing slash. |
key |
string | Ticket key, e.g. |
- Source:
Returns:
HTML string for the <a> element.
- Type
- string
Example
jiraKeyLinkHtml('https://x.atlassian.net/browse', 'AB-1')
// → '<a class="jira-key-link" href="https://x.atlassian.net/browse/AB-1" …>AB-1<span class="sr-only"> (opens in new tab)</span></a>'
jiraTicketHtml(text) → {string}
Returns HTML for a task text string, converting any leading Jira ticket key
(e.g. AITO-1234) into a clickable link. The remainder of the text is
HTML-escaped and appended.
Parameters:
| Name | Type | Description |
|---|---|---|
text |
string | Raw task text, possibly starting with a Jira key. |
- Source:
Returns:
HTML string.
- Type
- string
jumpToGapReportEntry(entryId, entryDate)
Jumps from the gap report straight to a flagged entry: closes the report, switches the viewed day and the Today's Flow tab to that entry's Log view, opens its proof-link/note editor, and scrolls the row into view. This is the "find it yourself, fix it yourself" action the report exists for.
Parameters:
| Name | Type | Description |
|---|---|---|
entryId |
string | ID of the entry to jump to. |
entryDate |
string | The entry's date (YYYY-MM-DD). |
- Source:
lastWeekday(year, month, weekday) → {Date}
Returns the date of the last occurrence of a weekday in the given month.
Parameters:
| Name | Type | Description |
|---|---|---|
year |
number | Full 4-digit year. |
month |
number | Month (1 = January … 12 = December). |
weekday |
number | Day of week (0 = Sunday … 6 = Saturday). |
- Source:
Returns:
- Type
- Date
load()
Loads all persistent state from localStorage into module-level variables. Invalid records are dropped per-item (rather than rejecting entire arrays) and any drops are reported via wlLog.warn so data-quality issues are visible in DevTools rather than silently disappearing. Falls back to the last snapshot if entries are missing from primary storage.
- Source:
loadBlocks()
Loads time blocks from localStorage into blocks, filtering invalid entries.
Drops are reported via wlLog.warn so data-quality issues are visible in DevTools.
Applies a one-time migration to shift existing block slots by +2 when the
time-block grid start time changed from 08:00 to 07:00.
- Source:
loadExpiryDates()
Loads iteration expiry dates from localStorage into _expiryDates.
Seeds localStorage with EXPIRY_SEED on first run.
- Source:
loadLocationMap() → {Record.<string, string>}
Reads the stored location map from localStorage. Returns an empty map (and logs a warning) if the value is missing or corrupt, so a single bad write can never break the header render.
- Source:
Returns:
Date-key → location-id map.
- Type
- Record.<string, string>
loadPlan() → {void}
Loads plan tasks from localStorage into the shared plan-task list, filtering
out invalid entries via validPlanTask. Drops are reported via wlLog.warn so
data-quality issues are visible in DevTools. Resets to empty array on parse error.
Each task is
{ id, text, status, tag, date, [billable], [notionUrl], [emoji], [checkpoints], [parentId], [priority], [note] }.
- Source:
Returns:
- Type
- void
loadSprintLog() → {void}
Loads sprint history from localStorage into the module-level sprintLog array.
Falls back to an empty array and logs a warning on parse failure.
- Source:
Returns:
- Type
- void
loadTrackers() → {void}
Loads the trackers array from localStorage into module state. Falls back to an empty array and logs a warning on parse failure.
- Source:
Returns:
- Type
- void
locationFor(map, dateKey) → {string}
Resolves the stored work location for a given day, falling back to the default when the day is unset or holds an unknown value.
Parameters:
| Name | Type | Description |
|---|---|---|
map |
Record.<string, string> | Date-key → location-id map. |
dateKey |
string | Day key in YYYY-MM-DD form (see dk). |
- Source:
Returns:
A valid location id present in WORK_LOCATIONS.
- Type
- string
Example
locationFor({ '2026-06-03': 'office' }, '2026-06-03') // → 'office'
locationFor({}, '2026-06-03') // → 'remote'
locationFor({ '2026-06-03': 'bogus' }, '2026-06-03') // → 'remote'
logUtilEntry(kind)
Logs a short well-known activity (break / lunch / meeting) as a new entry while keeping the active timer running. The active timer is NOT stopped so the user can resume without friction.
Parameters:
| Name | Type | Description |
|---|---|---|
kind |
'break' | 'lunch' | 'meeting' | Activity type. |
- Source:
markInputInvalid(input) → {void}
Flags a text input as rejected: red border via .input--invalid, exposed to
assistive tech through aria-invalid, and focused. The mark clears itself
on the next edit so a corrected value is not left looking wrong.
Safe to call multiple times without an intervening edit: the clear-on-edit listener is added only once per input.
Parameters:
| Name | Type | Description |
|---|---|---|
input |
HTMLInputElement | The input whose value was rejected. |
- Source:
Returns:
- Type
- void
markPlanReviewedThisWeek()
Marks the current ISO week as reviewed so the reminder banner won't reappear this week, then re-renders the banner (hides it). Called both by the banner's dismiss button and by closing the checklist — either counts as "reviewed," matching the dismiss semantics already used for the long-running-timer and end-of-day-export reminders.
- Source:
(async) mergeBackupEntries(file) → {Promise.<void>}
Merges entries from a JSON backup file into the current data set without
replacing any existing records. Only entries whose id is not already
present in localStorage are added. Categories, tasks, and all other backup
fields are ignored — this is an entries-only operation.
Intended for the "worked on another machine" case: the backup from the remote session contains new entries that are absent from the local store.
Parameters:
| Name | Type | Description |
|---|---|---|
file |
File | The .json backup file selected by the user. |
- Source:
Returns:
- Type
- Promise.<void>
mergeDevLog()
Merges the hardcoded DEV_CHANGES array into the persisted dev log in
localStorage, adding only entries whose id is not already stored.
Maintains chronological sort order by id.
- Source:
mergeNoteMaps(a, b) → {Object.<string, string>}
Combines two task-keyed note maps — e.g. plan-task notes and entry notes —
into one, concatenating notes for the same key with a newline so both
survive as separate note: lines in the exported text (see
formatGroupedLines, which splits each map value on \n).
Parameters:
| Name | Type | Description |
|---|---|---|
a |
Object.<string, string> | First note map (rendered first). |
b |
Object.<string, string> | Second note map, appended after |
- Source:
Returns:
Combined map.
- Type
- Object.<string, string>
Example
mergeNoteMaps({ x: 'from task' }, { x: 'from entry' })
// → { x: 'from task\nfrom entry' }
migrateEntryDatesToLocal()
Fixes entry date fields that were stored using UTC midnight instead of the
user's local calendar date.
Background: dk() previously called toISOString() (UTC). For UTC+ users
(e.g., Finland, UTC+2/+3), entries logged between local midnight and 02:00–03:00
were stored under the previous day's UTC date. This migration re-derives date
from the entry's ts timestamp using the now-correct local dk().
Safe to run multiple times — entries already on the correct local date are untouched.
- Source:
migrateStorage()
Runs all pending schema migrations. Safe to call multiple times — migrations are skipped if the source key is absent or the destination key already has data.
- Source:
mlDaysInMonth(y, m) → {number}
Returns the number of days in a given month.
Parameters:
| Name | Type | Description |
|---|---|---|
y |
number | Full year (e.g. 2026). |
m |
number | Month index, 0-based (0 = January). |
- Source:
Returns:
Day count (28–31).
- Type
- number
mlHeatColor(hours) → {string}
Maps a logged-hours value to a CSS colour for the heatmap grid. Thresholds: 0h → bg3 (empty), <2h → faint blue, <5h → mid blue, <7h → strong blue, ≥7h → solid blue.
Parameters:
| Name | Type | Description |
|---|---|---|
hours |
number | Total logged hours for a single day. |
- Source:
Returns:
A CSS colour value (variable or rgba/hex string).
- Type
- string
mlHoursForDay(dateKey) → {number}
Sums tracked milliseconds for all non-cancelled entries on a given day.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string |
- Source:
Returns:
Total hours (as a float).
- Type
- number
mlMonthPrefix(year, month) → {string}
Builds the YYYY-MM prefix that entries and tasks are matched against.
Parameters:
| Name | Type | Description |
|---|---|---|
year |
number | Full year (e.g. 2026). |
month |
number | Month index, 0-based (0 = January). |
- Source:
Returns:
Zero-padded prefix, e.g. 2026-05.
- Type
- string
mondayOfWeek(tsopt) → {number}
Returns the Unix timestamp (ms) of Monday 00:00 local time for the week
containing ts — the app's calendar-week boundary, matching the "Week
N/52" header label (getISOWeek() in 09-clock-weather.js).
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
ts |
number |
<optional> |
Reference time in ms; defaults to |
- Source:
Returns:
Monday 00:00 local time, in ms.
- Type
- number
Example
// 2026-06-03 is a Wednesday → Monday of that week is 2026-06-01
new Date(mondayOfWeek(new Date(2026, 5, 3, 14, 30).getTime())).toDateString()
// → 'Mon Jun 01 2026'
// 2026-06-01 is already a Monday → returns that same day at 00:00
new Date(mondayOfWeek(new Date(2026, 5, 1, 9, 0).getTime())).getHours() // → 0
moveBlockToSlot(blockId, requestedSlot, dateKey) → {'moved'|'unchanged'|'declined'|'missing'}
Moves a planned block to another start slot. This is the one place that changes a block's time, so drag-and-drop and the keyboard handler clamp, warn about overlaps, save and redraw identically.
Parameters:
| Name | Type | Description |
|---|---|---|
blockId |
string | ID of the block to move. |
requestedSlot |
number | Wanted start slot; clamped into the grid. |
dateKey |
string | YYYY-MM-DD of the day in view, for the overlap check. |
- Source:
Returns:
moved after saving and
redrawing; unchanged when the clamped slot is the current one (nothing is
saved or redrawn); declined when the user refused the overlap prompt;
missing when no block has that ID.
- Type
- 'moved' | 'unchanged' | 'declined' | 'missing'
moveTaskToColumn(taskId, newStatus) → {void}
Moves a task to a new board column, updating its status and timer state. Dropping into In Progress stops any running timer, creates a new time entry, and starts tracking. Dropping into Done or To Do stops the active timer.
Parameters:
| Name | Type | Description |
|---|---|---|
taskId |
string | The plan task ID to move. |
newStatus |
string | Target status: 'todo' | 'inprogress' | 'done'. |
- Source:
Returns:
- Type
- void
nearestWeekday(from, weekday) → {Date}
Returns the first date on or after from that falls on the given weekday.
Parameters:
| Name | Type | Description |
|---|---|---|
from |
Date | Start date (inclusive). |
weekday |
number | Day of week (0 = Sunday … 6 = Saturday). |
- Source:
Returns:
- Type
- Date
nextDistinctColor() → {string}
Returns the next visually distinct colour from the palette for a new category. Falls back to golden-angle HSL generation once all palette colours are in use.
- Source:
Returns:
A CSS colour string (hex or hsl).
- Type
- string
nextLocation(loc) → {string}
Returns the next location id when toggling, cycling through the keys of
WORK_LOCATIONS. With the two presets this flips Remote ↔ Office.
An unrecognised loc (indexOf → -1) is treated as "before the first" and
wraps to the first location, so the toggle always recovers to a valid state.
Parameters:
| Name | Type | Description |
|---|---|---|
loc |
string | Current location id. |
- Source:
Returns:
The following location id (wraps around).
- Type
- string
Example
nextLocation('remote') // → 'office'
nextLocation('office') // → 'remote'
nextLocation('bogus') // → 'remote' (unknown input recovers to the first)
normalizeCalendarMeeting(meeting) → {*}
Returns a copy of meeting with a subject the rest of the app can rely on.
Outlook returns no subject at all for an appointment saved without a title,
so the meeting arrives with subject: null and validCalendarMeeting
rejects it — an untitled meeting used to disappear from the strip entirely
rather than showing up unnamed. Normalising first keeps it, since its times
are what the strip actually needs.
The input is not modified; a non-object is returned unchanged so the caller's validator still gets to reject it.
Parameters:
| Name | Type | Description |
|---|---|---|
meeting |
* | Candidate meeting object from the calendar API response. |
- Source:
Returns:
A copy with a non-empty string subject, or the input unchanged.
- Type
- *
Example
normalizeCalendarMeeting({ subject: null, start: 'a', end: 'b' })
// → { subject: '(no title)', start: 'a', end: 'b' }
normalizeCalendarMeeting({ subject: ' Standup ', start: 'a', end: 'b' })
// → { subject: 'Standup', start: 'a', end: 'b' }
noteAreaHtml(t) → {string}
Builds the inline note display / edit area for a task row. Renders a read-only line when the task has a note and the area is closed; renders a textarea with save/remove/cancel when open.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
Object | The plan task. |
- Source:
Returns:
HTML string, or '' when no note and area is closed.
- Type
- string
noteBtnHtml(t) → {string}
Builds the note toggle button HTML for a task row. Visually distinct when the task already has a note.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
Object | The plan task. |
- Source:
Returns:
HTML button element.
- Type
- string
notifyPomodoroEnd() → {void}
Called from pomoDone() when the Pomodoro ring reaches zero.
Fires the registered _onSprintEnd callback if one is set, then clears it
so it cannot fire twice.
- Source:
Returns:
- Type
- void
notionBtnHtml(t) → {string}
Builds the Notion send/link button HTML for a task row. Shows a link icon if already sent; send icon otherwise.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
Object | The plan task. |
- Source:
Returns:
HTML button element.
- Type
- string
nthWeekday(year, month, weekday, n) → {Date}
Returns the date of the nth occurrence of a weekday in the given month.
Parameters:
| Name | Type | Description |
|---|---|---|
year |
number | Full 4-digit year. |
month |
number | Month (1 = January … 12 = December). |
weekday |
number | Day of week (0 = Sunday … 6 = Saturday). |
n |
number | 1-based occurrence index (1 = first, 2 = second, …). |
- Source:
Returns:
- Type
- Date
openBlockEmojiPicker(bid, anchor) → {void}
Opens a floating emoji picker anchored below anchor for a time block.
The picker is exposed as an ARIA dialog (WCAG 4.1.2 / 2.1.2): it carries
role="dialog", aria-modal, and aria-label; Tab navigation wraps
inside via trapFocusInOverlay(); Escape closes the picker and returns
focus to the trigger. Calling again for the same block ID closes the
picker (toggle).
Parameters:
| Name | Type | Description |
|---|---|---|
bid |
string | Block ID. |
anchor |
HTMLElement | Element to position the picker below. |
- Source:
Returns:
- Type
- void
openEodModal()
Opens the end-of-day modal for the day in view: auto-exports the time log and JSON backup, saves that day's EOD timestamp, populates handoff notes for its unfinished tasks, renders its dev changelog entries, and lists the test areas to review. Viewing an earlier day ends that day, not the current one.
- Source:
openExpiryModal()
Opens the iteration-expiry editor modal, pre-filling the textarea with the current expiry dates (one per line).
- Source:
openGapReportOverlay()
Opens the gap report: this calendar week's billable entries missing both a
proof link and a note. Builds the list fresh from current entries state
every time it's opened, so it always reflects the latest data.
- Source:
openIDB() → {Promise.<IDBDatabase>}
Opens (or creates) the IndexedDB database used to persist the FSA directory handle.
- Source:
Returns:
Resolves with the opened database instance.
- Type
- Promise.<IDBDatabase>
openMigration()
Opens the migration modal and populates it with open (unresolved) tasks for the current month. Alerts if there is nothing to migrate.
- Source:
openPlanReviewOverlay()
Opens the weekly plan review checklist, rebuilding it fresh from current data.
- Source:
openRapid()
Opens the quick-capture overlay and focuses the search input.
- Source:
openSprintSetup() → {void}
Shows the sprint setup panel, clears the intention input, resets the duration selection to 25 minutes, and focuses the intention field.
- Source:
Returns:
- Type
- void
openTrackerForm() → {void}
Shows and populates the new-tracker form inside #trackerNewForm. Pre-fills the colour picker with the first category's colour. Attaches Save / Cancel / keyboard listeners.
- Source:
Returns:
- Type
- void
openWeeklyReportOverlay() → {void}
Opens the weekly report draft: this calendar week's entries grouped by
ticket. Builds the text fresh from current entries state every time
it's opened, so it always reflects the latest data.
- Source:
Returns:
- Type
- void
parseHandoffNotes(raw) → {Object.<string, string>}
Parses the stored wl_handoff value into a prototype-less map of task key to
note. A prototype-less object keeps keys such as __proto__ and
constructor as ordinary own properties, so task names cannot reach
Object.prototype.
Parameters:
| Name | Type | Description |
|---|---|---|
raw |
string | null | Raw localStorage value (may be null or malformed JSON). |
- Source:
Throws:
-
If
rawis non-empty and not valid JSON. - Type
- SyntaxError
Returns:
Map of task key to note; empty if the value is missing or is not a plain JSON object (e.g. an array, string or null).
- Type
- Object.<string, string>
parseJiraLabel(label) → {Object}
Parses a task label into its Jira issue key and human-readable name.
Returns ticket: null when the label carries no leading Jira key.
Parameters:
| Name | Type | Description |
|---|---|---|
label |
string | Raw task label, e.g. |
- Source:
Returns:
Parsed parts.
- Type
- Object
Example
parseJiraLabel('PROJ-42: Fix login') // → { ticket: 'PROJ-42', name: 'Fix login' }
parseJiraLabel('Write tests') // → { ticket: null, name: 'Write tests' }
parseRapidTokens(raw, cats, nowopt) → {Object}
Parses inline shorthand tokens from a raw quick-capture input string and returns a clean text plus structured token values.
Supported tokens (each stripped from the returned text):
#<cat>— Category: matched against category ids and labels (case-insensitive; prefix match as fallback). Last occurrence wins.!<sig>— Signifier shortcode (see RAPID_SIG_SHORTCUTS). Last occurrence wins.><date>— Date pointer: today, tomorrow, YYYY-MM-DD, or weekday mon–sun (next occurrence). Last occurrence wins.
Unrecognised tokens are left in the text unchanged so the user sees them and can correct them without data being silently discarded.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
raw |
string | Raw input text that may contain inline shorthand tokens. |
|
cats |
Array.<{id: string, label: string}> | Available categories for |
|
now |
Date |
<optional> |
Reference date for relative date resolution; defaults to new Date(). |
- Source:
Returns:
- Type
- Object
partitionSessionNotes(allItems) → {Object}
Partitions a flat item list from buildDailyLogItems into two structures:
non-session-note items (the main timeline rows) and a lookup of session-note
items keyed by their parentEntryId. Session-notes render nested inside
their parent entry row rather than as standalone timeline entries.
Parameters:
| Name | Type | Description |
|---|---|---|
allItems |
Array.<object> | Items returned by buildDailyLogItems. |
- Source:
Returns:
- Type
- Object
patchCarriedTasks()
Applies data migrations and status patches to today's plan tasks after carry-over:
- Stamps
billable: trueon tasks/categories that predate the feature. - Stamps
completedAton done tasks missing a timestamp. - Promotes today's task status to match the most recent past version when that version was pending/blocked/upcoming or in-progress.
- Source:
pausePomo()
Pauses the pomodoro timer without resetting the remaining time.
- Source:
pauseTimer()
Pauses the running timer, accumulating elapsed time so it can be resumed. No-ops if no timer is active or it is already paused.
- Source:
pickSaveFolder() → {Promise.<void>}
Prompts the user to select a save folder via the File System Access API and persists the resulting directory handle. Shows a fallback alert in browsers that do not support the API.
- Source:
Returns:
- Type
- Promise.<void>
pickableCategories(categories, keepId) → {Array.<EpicCategory>}
Filters a category list down to the epics a picker should offer. Archived
epics are hidden, except for keepId — the currently selected epic stays
visible even once archived so an entry already tagged with it still renders
its own selection instead of appearing untagged.
Parameters:
| Name | Type | Description |
|---|---|---|
categories |
Array.<EpicCategory> | All known epics. |
keepId |
string | null | Epic ID to keep regardless of archived state; omit or pass null to hide every archived epic. |
- Source:
Returns:
The epics to offer, in input order.
- Type
- Array.<EpicCategory>
planReviewWeekBounds() → {Object}
Returns this ISO week's [startKey, endKey) as YYYY-MM-DD date-string bounds, using the existing mondayOfWeek()/dk() helpers.
- Source:
Returns:
- Type
- Object
playChime()
Plays two soft sine-wave tones (528 Hz then 660 Hz) using the Web Audio API to signal an elapsed-time milestone. Silently skips if Web Audio is unavailable (e.g. browser privacy settings).
- Source:
playPomoBeep()
Plays three short 660 Hz beeps spaced 350 ms apart using the Web Audio API to signal pomodoro completion. Silently skips if Web Audio is unavailable.
- Source:
pomoAddTime()
Adds 120 seconds to the current running session without resetting segments. No-op if the timer is not running.
- Source:
pomoAffirmation(totalopt, leftopt) → {string}
Returns a progress-aware affirmation string based on elapsed time. Default parameters let callers pass explicit values (useful for testing).
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
total |
number |
<optional> |
Total session seconds; defaults to pomoTotal. |
left |
number |
<optional> |
Remaining seconds; defaults to pomoLeft. |
- Source:
Returns:
Short motivational phrase.
- Type
- string
pomoBuildDateRange() → {Array.<string>}
Builds an array of POMO_SPARKLINE_DAYS date strings ending today, oldest first.
- Source:
Returns:
Array of YYYY-MM-DD strings.
- Type
- Array.<string>
pomoDone()
Called when the pomodoro timer reaches zero. Plays the completion beep, briefly animates the time display, and appends a session record to the pomodoro log in localStorage.
- Source:
pomoGap(n) → {number}
Returns the angular gap (in radians) between pomodoro timer segments. Smaller gaps are used for higher segment counts to keep them visually distinct.
Parameters:
| Name | Type | Description |
|---|---|---|
n |
number | Number of segments (minutes). |
- Source:
Returns:
Gap in radians.
- Type
- number
pomoGetLog() → {Array.<PomoSessionEntry>}
Reads and validates the pomodoro session log from localStorage.
Invalid records are dropped and reported via wlLog.warn.
- Source:
Returns:
Validated session log entries, newest first.
- Type
- Array.<PomoSessionEntry>
pomoSaveSession(session)
Prepends a new session entry to the pomodoro log and persists it. The log is capped at 100 entries (oldest are dropped).
Parameters:
| Name | Type | Description |
|---|---|---|
session |
PomoSessionEntry | The session entry to record. |
- Source:
pomoSessionsOnDate(log, dateKey) → {number}
Returns the number of completed pomodoro sessions on the given date.
Parameters:
| Name | Type | Description |
|---|---|---|
log |
Array.<PomoSessionEntry> | Session log. |
dateKey |
string | Date in YYYY-MM-DD format. |
- Source:
Returns:
- Type
- number
pomoSparkColours() → {Object}
Reads the --pomo-spark-fill and --pomo-spark-empty CSS variables for the active colour scheme. Falls back to hardcoded values when CSS vars are unavailable (e.g. headless test environments).
- Source:
Returns:
- Type
- Object
pomoTapOut()
Ends the session early, logs a partial session, and transitions to done state. Records the elapsed minutes (minimum 1) so the session appears in the log.
- Source:
positionNowLine()
Positions the "now" indicator line in the time-block grid to reflect the
current time. Hides the line when outside the grid's time range
(TB_START–TB_END).
- Source:
prioBtnHtml(t) → {string}
Builds the priority toggle button HTML for a task row. Click cycles: normal (0) → high (1) → low (-1) → normal.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
Object | The plan task. |
- Source:
Returns:
HTML button element.
- Type
- string
promoteMatchingTaskToInProgress(text) → {void}
Promotes today's plan task matching text to "in progress", if one
exists and is still in a promotable state. Called whenever a timer starts
on text that might correspond to an existing board card — the hero
composer, a recent chip, the log capture input, and the board's own
"track" button all funnel through this so the Kanban board stays in sync
no matter which control was used to start tracking.
Parameters:
| Name | Type | Description |
|---|---|---|
text |
string | The entry text a timer was just started on. |
- Source:
Returns:
- Type
- void
purgeLegacyAnthropicKey() → {void}
Removes an Anthropic API key left in localStorage by an older version, which kept it in the browser. The key now lives server-side only, so any copy still in the browser is a leftover to delete rather than honour.
- Source:
Returns:
- Type
- void
readCollapseState(sectionId, defaultCollapsed) → {boolean}
Reads the stored collapsed state for a section from localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
sectionId |
string | The section element's |
defaultCollapsed |
boolean | Value to use when nothing is stored. |
- Source:
Returns:
Whether the section should be collapsed.
- Type
- boolean
readHiddenMeetingKeys(dayKey) → {Array.<string>}
Reads the occurrence keys of the meetings hidden for one day.
Parameters:
| Name | Type | Description |
|---|---|---|
dayKey |
string | Day key from |
- Source:
Returns:
Stored keys, or an empty array if none are stored.
- Type
- Array.<string>
readOptionalLogForBackup(storeKey, label) → {Array}
Reads and parses an optional JSON-array log from localStorage for inclusion in a backup. If the stored value is present but not valid JSON, the failure is logged via wlLog (so the data loss is diagnosable rather than silent) and an empty array is returned so the rest of the backup still succeeds. Absent keys legitimately yield an empty array.
Parameters:
| Name | Type | Description |
|---|---|---|
storeKey |
string | The localStorage key to read. |
label |
string | Human-readable log name for the warning message. |
- Source:
Returns:
The parsed array, or [] if absent or unparseable.
- Type
- Array
refreshEpicPickers() → {void}
Re-renders every surface that offers an epic picker, so an archive or restore is reflected everywhere at once rather than only in the modal.
- Source:
Returns:
- Type
- void
refreshPomoDashboard()
Full Pomodoro dashboard refresh: redraws the 28-day sparkline, ribbon footer, and composer task label. Called on module load and after every session completion.
- Source:
removeBlockById(blockId) → {void}
Removes one time block, persists the remaining blocks and redraws the grid. Shared by the block's delete button and the start prompt's "no" branches so all three drop a block the same way.
Parameters:
| Name | Type | Description |
|---|---|---|
blockId |
string | ID of the block to remove. |
- Source:
Returns:
- Type
- void
removeRetiredKeys() → {number}
Deletes localStorage keys left behind by removed features. Safe to call multiple times — absent keys are skipped. A storage failure on one key is logged and the sweep continues: this is best-effort cleanup, and an orphaned key is never worth aborting page load over.
- Source:
Returns:
How many keys were actually removed.
- Type
- number
render()
Full application re-render: updates the date label, timer bar, stat counters, sub-stats, time-log list, chart, quick-pick, plan, completed section, and time-block view. Call whenever persistent state changes.
Design trade-off: full DOM re-render on every change rather than targeted
updates. Keeps state reasoning simple for a single-user personal tool where
the entry list is small (typically < 50 items per day). If performance becomes
a concern, the innermost timelineEl.querySelectorAll event-binding loop
(bindTimelineEntryEvents in 04c-render-timeline.js) is the first candidate
for optimisation.
- Source:
renderBoardDoneHistory(doneListEl, viewKey)
Appends the collapsible "older history" expander to the Done column list. Shows completed tasks from prior dates, grouped by day, within the active iteration.
Parameters:
| Name | Type | Description |
|---|---|---|
doneListEl |
HTMLElement | The |
viewKey |
string | The current view date key (YYYY-MM-DD). |
- Source:
renderCalStrip(meetings)
Renders the calendar meetings strip for today. Sorts meetings by start time, marks past meetings grey/italic, pulses ongoing meetings, and provides ▶ start and ✕ hide buttons per meeting.
Parameters:
| Name | Type | Description |
|---|---|---|
meetings |
Array.<Object> | Array of meeting objects from the PS server. |
- Source:
renderCompleted()
Renders the completed-tasks section for the currently viewed date. Shows tasks that were completed on or before the view date and whose iteration expiry date has not yet passed. Deduplicates by task text, keeping only the most recently completed version. Hides the section when there are no matching tasks.
- Source:
renderDayStrip(dateKey)
Renders the compact day-overview strip: hour-tick labels, entry footprints, and (today only) a live "now" cursor.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | YYYY-MM-DD. |
- Source:
renderEmergencyCps()
Renders the checkpoint list inside the focus-mode overlay for the currently active task. Hides the wrapper if the task has no checkpoints. Each checkpoint cycles through three states on click: false → 'partial' → true.
- Source:
renderEodBtn()
Updates the "end the day" button to show the end time recorded for the day in view (if any) or its default label, and dims the button once a time is set.
- Source:
renderEodReminder()
Shows or hides the end-of-day export reminder banner. A nudge, not an
automatic export — the app never writes a file without an explicit click,
so this only ever asks. Always evaluated against today, regardless of
which day is currently in view (viewDate), since the reminder is about
the actual current workday.
- Source:
renderEodTimesheet(dateKey)
Fills the End of Day timesheet form with today's draft entry. With nothing tracked the form is disabled rather than submitting an empty day.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | Day being ended, |
- Source:
renderEpicsManager() → {void}
Fills the epics modal with the archived-epic list and binds one restore button per row.
Rows are keyed by list index (epicRestore-0, epicRestore-1, …) rather
than by epic ID: IDs come from persisted data and would need escaping to be
safe in an attribute and matching unescaped for the lookup, so an index
sidesteps that mismatch entirely.
- Source:
Returns:
- Type
- void
renderFlowHeader(dateKey, activeView)
Renders the section header: icon, title, tracked/billable totals, and the Flow / Log / Blocks segmented control.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | |
activeView |
'flow' | 'log' | 'blocks' |
- Source:
renderFlowView(dateKey)
Renders the Flow view: a vertical list where each entry's accent strip height is proportional to its duration (height = max(64, 0.6 × minutes) px), giving longer tasks more visual weight.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string |
- Source:
renderFolderStatus() → {void}
Updates the #folderStatus element to show the currently selected save
folder name (green) or a "pick save folder" prompt (default colour).
- Source:
Returns:
- Type
- void
renderGapReminder(dateKey)
Renders or hides the gap-reminder banner.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string |
- Source:
renderHeaderAndTimerSection()
Updates the date label and prev/next-day navigation, the location chip, the start/end-of-day controls and reminder, and the timer bar/button — everything render() needs to refresh purely because the viewed date or timer state may have changed, before it gets to stats or the timeline.
- Source:
renderHeaderStatTiles() → {void}
Renders the three header stat tiles (distinct tasks today / distinct epics this week / current streak) and the collapsed one-line summary that mirrors them.
- Source:
Returns:
- Type
- void
renderHeroCard()
Switches the root card's state-modifier class and makes the matching inner panel visible. Updates all dynamic content for the current state.
- Source:
renderLocation() → {void}
Updates the date-nav location button to reflect the viewed day's location. No-ops when the button is absent (e.g. in a reduced test DOM).
- Source:
Returns:
- Type
- void
renderLongRunningWarning(elapsedMs)
Shows or hides the long-running-timer warning banner in the Hero Card's running panel. Purely visual — never stops or modifies the timer itself; the user must click "stop timer" for that. Stays hidden for the rest of this timer session once dismissed (reset by the next startTimer() call).
Called every second from tickTimer(); rebuilds the banner's innerHTML only
when the displayed "Xh Ym" label actually changes (once a minute), not on
every tick. The container is role="alert", so a per-second DOM rewrite
would make screen readers re-announce it every second — this keeps
announcements to once per minute, matching the visible text change.
Parameters:
| Name | Type | Description |
|---|---|---|
elapsedMs |
number | Elapsed timer time in milliseconds. |
- Source:
renderMigrationStep()
Renders the current migration step (or the "done" screen when all resolved).
- Source:
renderMonthlyCalendar(calEl, year, month) → {void}
Renders the heatmap calendar into calEl: builds its markup, then binds its
events. Kept as the single entry point so callers never render without binding.
Parameters:
| Name | Type | Description |
|---|---|---|
calEl |
HTMLElement | The |
year |
number | Full year to render. |
month |
number | Month index, 0-based. |
- Source:
Returns:
- Type
- void
renderMonthlyLog() → {void}
Orchestrates the Monthly Log view: resolves DOM targets and delegates each panel to a single-purpose renderer.
- Source:
Returns:
- Type
- void
renderMonthlySummary(sumEl, monthPrefix) → {void}
Renders the time-totals summary panel: total logged, billable, top category.
Writes its full HTML to sumEl. No event binding. Early-returns if sumEl
is absent so a partial DOM doesn't throw on innerHTML assignment.
Parameters:
| Name | Type | Description |
|---|---|---|
sumEl |
HTMLElement | The |
monthPrefix |
string | Date prefix |
- Source:
Returns:
- Type
- void
renderMonthlyTasks(taskEl, monthPrefix) → {void}
Renders the task-inventory panel: open / done / migrated counts plus
a "Run Migration" button. Writes its full HTML to taskEl and binds
the button to openMigration() if that helper is loaded. Early-returns
if taskEl is absent so a partial DOM doesn't throw on innerHTML.
Parameters:
| Name | Type | Description |
|---|---|---|
taskEl |
HTMLElement | The |
monthPrefix |
string | Date prefix |
- Source:
Returns:
- Type
- void
renderMoon()
Renders today's moon phase, illumination, and zodiac sign into #liveMoon.
No-ops if the element is absent.
- Source:
renderPlan()
Re-renders the entire plan UI as a 3-column kanban board (To Do / In Progress / Done). Pending and blocked tasks absorb into the To Do column with their existing badge treatment. The Done column shows today's completed tasks and a collapsible history expander for older ones.
Design trade-off: full DOM re-render on every state change rather than targeted updates. Acceptable for a personal tool where the task list is small (< 20 items).
- Source:
renderPlanReviewList()
Rebuilds the checklist list + subtitle from current planTasks state. Shared by the initial open and by each row action, since an action removes exactly one task from the underlying data and the list should reflect that immediately rather than requiring a re-open.
- Source:
renderPlanReviewReminder()
Shows or hides the weekly plan review reminder banner. A nudge, not an automatic reconciliation — this app has no live Jira connection, so only the user can confirm a planned task is still accurate. Hidden once the current ISO week has already been marked reviewed, or when there's nothing to review.
- Source:
renderPomoLog()
Renders the pomodoro session history list inside #pomoLog.
Shows date/time, duration, and the task that was active during each session.
- Source:
renderPomoRibbon()
Renders the ribbon footer below the 4-column grid:
#pomoRibbonDots: last-5-session dot sequence (● filled, · empty slot)#pomoRibbonPill: "Peak Focus" pill when today matches the all-time high, otherwise "N sessions today", hidden when no sessions today#pomoRibbonLink: "View all N sessions ↓" button that scrolls#pomoLog
Each element is individually guarded — no-op when absent from the DOM.
- Source:
renderPomoSparkline()
Draws a 28-day focus density bar chart on the #pomoSparkline canvas.
Each bar represents one calendar day; bar height is proportional to the
number of completed pomodoro sessions on that day.
No-op when the canvas element is absent or the 2D context is unavailable.
- Source:
renderQuickPick()
Renders the "recent tasks" quick-pick bar below the capture input. Deduplicates entries by text, hides manually-dismissed tasks and tasks past their iteration expiry, and caps the list at 16 items.
- Source:
renderRollingSummary() → {void}
Renders the 7-day rolling summary into #tfSummaryPane. No-ops when the pane element is absent (e.g. reduced test DOM).
- Source:
Returns:
- Type
- void
renderRow(t) → {string}
Builds the HTML string for a single plan task row. Handles two layout branches: pending/blocked (compact) and normal (full). Reads module-level state variables for edit mode, checkpoint open state, and pending comment state so re-renders are always consistent.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
Object | The plan task object to render. |
- Source:
Returns:
HTML string for one .plan-item element (and optional split row).
- Type
- string
renderSodBtn() → {void}
Updates the session chip to show the start time recorded for the day in view (with a green dot), or the default "start the day" label when that day has no recorded start. The button's text content is its accessible name; no static aria-label is used so screen readers announce the current state ("started HH:MM" or "start the day").
- Source:
Returns:
- Type
- void
renderSprintDurations() → {void}
Renders the duration chip row inside #sprintDurations, marking the currently selected duration active. Attaches click listeners to each chip.
- Source:
Returns:
- Type
- void
renderSubStatTiles() → {void}
Renders the three sub-stat tiles beneath the header stats: today's most-tracked task, this week's most-tracked task, and the streak day with the longest tracked time. Hides each tile when there's no data for it.
- Source:
Returns:
- Type
- void
renderTagRow() → {void}
Renders the epic tag row and rebinds its listeners. Every state change in the handlers below funnels back through here, so the row is always rebuilt wholesale rather than patched in place.
- Source:
Returns:
- Type
- void
renderTimeblock()
Renders the full time-block grid for the currently viewed date: time labels, grid rows, planned blocks (with drag-to-move), live timer block, a "now" line, and the plan-task drag targets. Also handles drag-and-drop wiring for moving existing blocks and dropping tasks from the plan list.
- Source:
renderTimelineSection(list) → {void}
Builds and mounts the #timeline entry list (or its empty state) plus the ad-hoc log row, then binds every entry-row event handler and renders the sections that depend on the timeline. Mirrors render()'s old two branches: an empty list bails out early after rendering its own sub-components, exactly as before.
Parameters:
| Name | Type | Description |
|---|---|---|
list |
Array.<Object> | viewEntries() result for the currently viewed date. |
- Source:
Returns:
- Type
- void
renderTodayFlow()
Renders the Today's Flow section: header, day-overview strip, gap reminder, and the active view pane (Flow / Log / Blocks / Month). Called from render() on every state change and when the view toggle fires.
- Source:
renderTrackRecent()
Renders the "+ TRACK RECENT" strip inside #planTrackRecent.
Shows chips for the most recent unique time-log entries from today so the
user can restart a timer with a single click without re-typing.
Limits to 5 entries; hidden when no entries exist for today.
- Source:
renderTrackers() → {void}
Renders all tracker cards into #trackerList. Each card shows a 28-day hit/partial/miss grid, current streak, and total hit count. Attaches delete-button listeners after render.
- Source:
Returns:
- Type
- void
resetPomo()
Resets the pomodoro timer to the full configured duration without starting it.
- Source:
resolveCarryStatus(todayTask, prev) → {string|null}
Determines the carry-forward status a today-task should adopt based on its most recent past peer, implementing the following rules:
pendingorblockedprev overridestodoorinprogresstoday — the task is still blocked so the blocking state wins.upcomingprev overrides only atodotoday — a todo placeholder created from an even-older copy should reflect the more recent intent to defer. It must NOT overrideinprogress, because the user explicitly started the task and a reload must not undo that.inprogressprev promotes atodotoday — the task was already being worked on and the carry placeholder should show that.
Returns null when no change is needed.
Parameters:
| Name | Type | Description |
|---|---|---|
todayTask |
Object | Today's task object. |
prev |
Object | Most recent past task with the same text (case-insensitive). |
- Source:
Returns:
The new status to apply, or null for no change.
- Type
- string | null
Example
resolveCarryStatus({ status: 'todo' }, { status: 'upcoming' }) // → 'upcoming'
resolveCarryStatus({ status: 'inprogress' }, { status: 'upcoming' }) // → null
resolveCarryStatus({ status: 'todo' }, { status: 'pending' }) // → 'pending'
resolveEodTimestamp(viewDay, timedEntries, now) → {number}
Picks the timestamp recorded when a day is ended. Ending today stamps "now". Ending an earlier day stamps the end of its last timed entry, or the end of that calendar day when it has none — "now" would fall on a different day and misreport when the viewed day finished.
Parameters:
| Name | Type | Description |
|---|---|---|
viewDay |
Date | The day being ended. |
timedEntries |
Array.<Object> | The ended day's entries, each with |
now |
number | Current time as a Unix timestamp (ms). |
- Source:
Returns:
Unix timestamp (ms) to store as the day's end.
- Type
- number
resolveRapidDate(token, nowopt) → {string|null}
Resolves a >date shorthand token to a YYYY-MM-DD date key.
Supported tokens: 'today', 'tomorrow', exact 'YYYY-MM-DD', and weekday abbreviations
'mon'–'sun' (resolves to the next occurrence, never today).
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
token |
string | Raw date token (without the leading |
|
now |
Date |
<optional> |
Reference date for relative resolution; defaults to new Date(). |
- Source:
Returns:
Resolved date key, or null if the token is unrecognised.
- Type
- string | null
restoreArchivedCategory(categories, catId) → {Array.<EpicCategory>}
Returns a copy of categories with the archived flag cleared from one
epic, bringing it back into the pickers.
Parameters:
| Name | Type | Description |
|---|---|---|
categories |
Array.<EpicCategory> | All known epics. |
catId |
string | Epic ID to restore. |
- Source:
Returns:
A new array with the epic un-archived.
- Type
- Array.<EpicCategory>
restoreGapReportFocus()
Restores focus to whatever triggered the report, then clears it.
- Source:
restorePlanReviewFocus()
Restores focus to whatever triggered the checklist, then clears it.
- Source:
restoreWeeklyReportFocus() → {void}
Restores focus to whatever triggered the report, then clears the trigger.
- Source:
Returns:
- Type
- void
resumeTimer()
Resumes a paused timer from where it left off. No-ops if no timer is active or it is not paused.
- Source:
resumeTimerIfActive()
Called at startup to reconnect the tick interval when the app is reloaded with an active timer persisted in localStorage. If the entry the timer was tracking no longer exists the timer is cleared. No-ops if no timer is active.
- Source:
rollingDateKeys(nopt) → {Array.<string>}
Returns the last n YYYY-MM-DD date keys ending at (and including) today,
ordered most-recent-first.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
n |
number |
<optional> |
7 |
- Source:
Returns:
- Type
- Array.<string>
roundToNearest30(ts) → {number}
Rounds a timestamp to the nearest 30-minute clock mark.
Tie-breaking rule (at exactly 15 min into a 30-min slot): rounds DOWN. Rationale: conservative for billing — a task must exceed the midpoint of the slot before the next slot is claimed.
Assumption: rounding ties (exactly 15 min past a slot start) are resolved in favour of the earlier slot to avoid inflating billable time. This matches the intent documented in DATA.md under wl_entries.ts.
Parameters:
| Name | Type | Description |
|---|---|---|
ts |
number | Unix timestamp in milliseconds. |
- Source:
Returns:
Rounded Unix timestamp in milliseconds.
- Type
- number
roundToNearest30IfBillable(ts, entry) → {number}
Rounds ts to the nearest 30-minute mark only when entry is billable.
Non-billable entries keep their exact timestamps for accurate reporting.
Parameters:
| Name | Type | Description |
|---|---|---|
ts |
number | Unix timestamp in milliseconds. |
entry |
Object | null | Work-log entry; if null or undefined, always rounds. |
- Source:
Returns:
Timestamp, conditionally rounded.
- Type
- number
Example
roundToNearest30IfBillable(1000 * 60 * 47, null) // → rounded (null always rounds)
roundToNearest30IfBillable(1000 * 60 * 47, { billable: false }) // → exact ts (non-billable)
roundToNearest30IfBillable(1000 * 60 * 47, { billable: true }) // → rounded (billable)
safeCssColor(c) → {string}
Returns c if it is a safe CSS colour (hex or hsl()), otherwise returns a neutral fallback.
Prevents malformed user-supplied colour values from breaking layout or injecting CSS.
Parameters:
| Name | Type | Description |
|---|---|---|
c |
string | CSS colour string to validate. |
- Source:
Returns:
A safe CSS colour string.
- Type
- string
Example
safeCssColor('#7B61FF') // → '#7B61FF'
safeCssColor('hsl(200,60%,50%)') // → 'hsl(200,60%,50%)'
safeCssColor('red') // → '#888780' (name blocked)
safeCssColor('') // → '#888780'
safeRoundedStart() → {number}
Returns a rounded start timestamp that does not overlap any existing entry
for today. Rounds Date.now() to the nearest 30-minute boundary, then
takes the maximum of that value and the latest tsEnd among today's
completed entries, preventing new entries from appearing to start before
a prior entry's end time.
- Source:
Returns:
Unix timestamp in milliseconds.
- Type
- number
save()
Persists entries, active timer, and categories to localStorage. Refuses to overwrite existing non-empty data with an empty array to guard against accidental data loss if save() is called before load() completes.
Assumption: an empty entries array in memory while localStorage still holds
data means save() was called before load() finished (e.g. a race during init),
not that the user intentionally deleted everything. Intentional clearing goes
through a dedicated wipe path that bypasses this guard.
If localStorage.setItem throws (e.g. QuotaExceededError), the failure is caught so it never propagates to save()'s many callers — silently losing data with no signal to the user is worse than a caught, logged, and surfaced failure. A persistent banner tells the user to export a backup; it clears itself automatically the next time a save() call succeeds.
- Source:
saveBlocks()
Persists the current blocks array to localStorage.
- Source:
saveEodHandoffNotes()
Reads all handoff-note inputs in the EOD modal and persists their values to
the wl_handoff localStorage key. Empty values are removed from the map.
- Source:
saveExpiryDates()
Reads the expiry-date textarea, validates each line against YYYY-MM-DD format, deduplicates and sorts the valid dates, persists them to localStorage, and closes the modal. Invalid lines are surfaced in the feedback element but not saved.
- Source:
saveHandoffNote(entryText, note)
Persists a handoff note for a given entry text. Pass an empty string or falsy value to delete the stored note. The key is stored case-insensitively.
Parameters:
| Name | Type | Description |
|---|---|---|
entryText |
string | The entry text to associate the note with. |
note |
string | The note to save; falsy removes the stored note. |
- Source:
saveLocationMap(map) → {void}
Persists the location map to localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
map |
Record.<string, string> | Date-key → location-id map. |
- Source:
Returns:
- Type
- void
saveMigrationRecord(rec)
Persists the migration completion record to localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
rec |
Object | Map of YYYY-MM → boolean. |
- Source:
savePlan() → {void}
Persists the current planTasks array to localStorage.
- Source:
Returns:
- Type
- void
saveSnapshot()
Writes a recoverable snapshot of today's log entries to localStorage every 30 minutes. The snapshot contains both a human-readable plaintext summary and the raw entry/category arrays so data can be recovered after accidental clearing. No-ops when there are no entries for today.
Assumption: 30 minutes is an acceptable data-loss window for a personal work log used in a single browser tab. Browser crashes, accidental page reloads, and mis-clicks on "clear data" are the main risks; all are adequately covered by a 30-minute recovery point. If higher durability is needed, reduce the interval in the setInterval call in 07-lifecycle.js.
- Source:
saveSprintLog() → {void}
Persists the current sprintLog array to localStorage.
- Source:
Returns:
- Type
- void
saveTaskNotionUrl(taskId, url)
Persists the Notion page URL on a plan task so the per-task button changes to an "open in Notion" link on next render.
Parameters:
| Name | Type | Description |
|---|---|---|
taskId |
string | Plan task ID. |
url |
string | Notion page URL returned by the API. |
- Source:
saveTrackerForm() → {void}
Reads the new-tracker form, validates inputs, pushes the tracker to the trackers array, persists it, and re-renders. Shows an alert if no category is selected; focuses the name field if the name is empty.
- Source:
Returns:
- Type
- void
saveTrackers() → {void}
Persists the current trackers array to localStorage.
- Source:
Returns:
- Type
- void
scheduleMigTask(task, dateStr)
Reschedules the task to the given date and marks it as migrated.
Parameters:
| Name | Type | Description |
|---|---|---|
task |
Object | |
dateStr |
string | YYYY-MM-DD |
- Source:
sectorPath(cx, cy, r, a1, a2) → {string}
Builds an SVG path string for a pie-chart sector (filled segment).
Parameters:
| Name | Type | Description |
|---|---|---|
cx |
number | Centre x coordinate. |
cy |
number | Centre y coordinate. |
r |
number | Radius. |
a1 |
number | Start angle in radians (0 = 12 o'clock). |
a2 |
number | End angle in radians. |
- Source:
Returns:
SVG path d attribute value.
- Type
- string
selectDevChangesForDay(devLog, dayKey) → {Array.<Object>}
Selects the dev-changelog entries recorded on a given day.
Parameters:
| Name | Type | Description |
|---|---|---|
devLog |
Array.<Object> | All dev-changelog entries ( |
dayKey |
string | The ended day, |
- Source:
Returns:
Entries dated that day.
- Type
- Array.<Object>
selectUnfinishedWorkedTasks(dayKey, entries, planTasks) → {Array.<Object>}
Selects the plan tasks to hand over at the end of a day: those dated that day, not done, and actually worked on (a log entry for the same text exists that day).
Parameters:
| Name | Type | Description |
|---|---|---|
dayKey |
string | The ended day, |
entries |
Array.<Object> | All log entries ( |
planTasks |
Array.<Object> | All plan tasks ( |
- Source:
Returns:
The unfinished tasks worked on that day.
- Type
- Array.<Object>
setActiveTimer(next)
Replaces the active timer state.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
object | null | New timer object, or null to clear it. |
setBlockEmoji(bid, emoji)
Saves an emoji to a time block and closes the picker. Pass null or an empty string to remove the block's emoji.
Parameters:
| Name | Type | Description |
|---|---|---|
bid |
string | Block ID. |
emoji |
string | null | Emoji character to assign, or null to remove. |
- Source:
setBlocks(next)
Replaces the time-blocks array.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Array | New blocks array. |
setCategories(next)
Replaces the categories array.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Array.<{id: string, label: string, color: string}> | New categories. |
setEntries(next)
Replaces the entries array.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Array | New entries array. |
setExportBackupCallback(fn)
Registers the exportBackup function so the save-failure banner's export button can invoke it. Must be called once during app startup, before any save() call that could fail.
Parameters:
| Name | Type | Description |
|---|---|---|
fn |
function | The exportBackup function from 05a-export.js. |
- Source:
setFavicon(state)
Updates the browser favicon to a coloured dot reflecting the timer state. Skips redundant redraws by tracking the last rendered state.
Parameters:
| Name | Type | Description |
|---|---|---|
state |
'active' | 'paused' | 'hyperfocus' | 'idle' | Current timer state. |
- Source:
setFlowView(view)
Persists the active view selection.
Parameters:
| Name | Type | Description |
|---|---|---|
view |
'flow' | 'log' | 'blocks' |
- Source:
setJiraBase(url) → {void}
Overrides the Jira base URL at runtime. Call this from 00-config.local.js.
Parameters:
| Name | Type | Description |
|---|---|---|
url |
string | Jira base URL, e.g. 'https://your-instance.atlassian.net/browse'. |
- Source:
Returns:
- Type
- void
setLogNotes(next)
Replaces the log-notes array.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Array | New log-notes array. |
setPlanTasks(next)
Replaces the plan-tasks array.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Array | New plan-tasks array. |
setPomoFavicon()
Redraws the browser favicon as a depleting wedge mirroring the remaining pomodoro time. Silently skips when the canvas API is unavailable.
- Source:
setSelectedTag(next)
Sets the currently selected tag.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
string | The new tag value. |
setSignifierRenderCallback(fn)
Registers the render function so cycleSignifier can re-render the UI after mutating an entry. Must be called once during app startup.
Parameters:
| Name | Type | Description |
|---|---|---|
fn |
function | The render() function from 04-render.js. |
- Source:
setTimerInterval(next)
Stores a new timer interval ID (or null to clear it).
Parameters:
| Name | Type | Description |
|---|---|---|
next |
number | null | The new interval ID. |
setTimesheetStatus(message, isErroropt)
Shows a status message under the timesheet form. Plain text only: the element is an aria-live region, so emoji would be read aloud to screen-reader users; the error modifier class carries the visual distinction instead.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
message |
string | Text to display. |
||
isError |
boolean |
<optional> |
false | Adds the error modifier class when true. |
- Source:
setTrackers(next)
Replaces the trackers array.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Array | New trackers array. |
setViewDate(next)
Replaces the view date.
Parameters:
| Name | Type | Description |
|---|---|---|
next |
Date | The new view date. |
setWeatherConfig(lat, lon, name) → {void}
Updates the weather location from the /api/config response. Each parameter is applied only when the server sends a valid value; missing or invalid values leave the corresponding default unchanged.
Parameters:
| Name | Type | Description |
|---|---|---|
lat |
number | undefined | Latitude (decimal degrees). |
lon |
number | undefined | Longitude (decimal degrees). |
name |
string | undefined | Display name for the location. |
- Source:
Returns:
- Type
- void
shiftViewDay(dayDelta) → {void}
Moves the viewed day by a whole number of days. Swaps in a fresh Date rather than mutating the current one, so anything still holding the previous viewDate keeps seeing the day it was given.
Parameters:
| Name | Type | Description |
|---|---|---|
dayDelta |
number | Days to move; negative goes back. |
- Source:
Returns:
- Type
- void
showHandoffInput()
Shows the handoff-note text field in the timer bar so the user can record what a future self (or colleague) should know before picking this task up again.
- Source:
showSaveFailureBanner()
Shows a persistent, dismissible banner warning that saving to localStorage is failing (e.g. quota exceeded), with a button to export a backup on the spot. Idempotent — a second failed save while the banner is already up does nothing, so repeat failures don't spam duplicate banners.
- Source:
showSprintReview() → {void}
Stops the main timer and renders the sprint review panel inside #sprintReview.
Populates the intention label, wires outcome buttons (yes / partly / no),
and on save: appends the sprint to sprintLog, tags the entry with the
outcome, persists both, hides the panel, and triggers a full render.
- Source:
Returns:
- Type
- void
sigHtml(entry) → {string}
Returns the HTML string for the clickable signifier widget on one entry row.
Parameters:
| Name | Type | Description |
|---|---|---|
entry |
Object | Log entry object. |
- Source:
Returns:
HTML string for a <span> button.
- Type
- string
sigSymbol(entry) → {string}
Returns the display symbol for an entry's signifier.
Parameters:
| Name | Type | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
entry |
Object | Log entry object. Properties
|
- Source:
Returns:
Unicode BuJo symbol (○ ★ → ✗ !) or '●' for the billable default.
- Type
- string
sigTitle(entry) → {string}
Returns the accessible title string for an entry's signifier.
Parameters:
| Name | Type | Description | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
entry |
Object | Log entry object. Properties
|
- Source:
Returns:
Accessible title (e.g. 'Meeting / event') or 'Billable' for the billable default.
- Type
- string
slotToTime(slot) → {string}
Converts a 0-based half-hour slot index to an "HH:MM" label.
Slot 0 = TB_START:00, slot 2 = TB_START+1:00, etc.
Parameters:
| Name | Type | Description |
|---|---|---|
slot |
number | 0-based slot index. |
- Source:
Returns:
"HH:MM" formatted time string.
- Type
- string
sodKey(dayopt) → {string}
Returns the localStorage key for a given day's start-of-day timestamp.
Parameters:
| Name | Type | Attributes | Default | Description |
|---|---|---|---|---|
day |
Date |
<optional> |
viewDate | Day to key by; defaults to the day in view so the session chip reflects whichever day the user has navigated to. |
- Source:
Returns:
Key in the format wl_sod_YYYY-MM-DD.
- Type
- string
startPomo()
Starts the pomodoro countdown. Resets to full duration if already at zero. Fires pomoDone and clears the interval when time runs out.
- Source:
startSprint() → {void}
Commits a new sprint: reads the intention from the input, creates a time-log
entry, starts the main timer, starts the Pomodoro for _sprintDuration
minutes, and sets _onSprintEnd so the review panel is shown when the ring
reaches zero. No-ops with a focus call if the intention field is empty.
- Source:
Returns:
- Type
- void
startTimer(entryId)
Starts (or restarts) the timer for the given entry. Clears any existing interval, resets the chime state, and begins a 1-second tick. Persists state and updates the UI immediately.
Parameters:
| Name | Type | Description |
|---|---|---|
entryId |
string | ID of the log entry to time. |
- Source:
statusOpts(cur) → {string}
Builds the
Parameters:
| Name | Type | Description |
|---|---|---|
cur |
string | The task's current status value. |
- Source:
Returns:
HTML option elements.
- Type
- string
stopTimer()
Stops the active timer, stamps the log entry with an end time (rounded to
the nearest 30 min for billable entries), clears activeTimer, and
triggers a full render. Resets the timer bar colour and closes the park
capture input if open. No-ops if no timer is active.
- Source:
storeDirHandle(handle) → {Promise.<void>}
Persists a File System Access directory handle to IndexedDB for reuse across sessions, and updates the in-memory cache.
Parameters:
| Name | Type | Description |
|---|---|---|
handle |
FileSystemDirectoryHandle | The directory handle to store. |
- Source:
Returns:
- Type
- Promise.<void>
stripPct(mins) → {number}
Converts a minutes-from-midnight value to a percentage across the strip range. Clamped to [0, 100].
Parameters:
| Name | Type | Description |
|---|---|---|
mins |
number |
- Source:
Returns:
- Type
- number
(async) submitEodTimesheet() → {Promise.<void>}
Posts the (possibly edited) entry to the local server and reports the outcome. An expired SSO session gets its own message with the fix.
- Source:
Returns:
Resolves once the status message is set.
- Type
- Promise.<void>
summaryGetEodTs(dateKey) → {number|null}
Reads the EOD timestamp for a date key from localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string |
- Source:
Returns:
- Type
- number | null
summaryGetSodTs(dateKey) → {number|null}
Reads the SOD timestamp for a date key from localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string |
- Source:
Returns:
- Type
- number | null
tbOverlaps(newStartMins, newEndMins, dateKey, excludeIdopt) → {string}
Returns a comma-separated string of task names that overlap a proposed time range, checking both planned blocks and logged time entries. Returns an empty string if there are no overlaps.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
newStartMins |
number | Proposed start time in minutes from midnight. |
|
newEndMins |
number | Proposed end time in minutes from midnight. |
|
dateKey |
string | Date string in YYYY-MM-DD format. |
|
excludeId |
string |
<optional> |
Block ID to exclude from the check (when moving). |
- Source:
Returns:
Overlapping task names, or '' if none.
- Type
- string
tbStartBlock(blockId, overrideTsopt)
Starts a timer for the given time block: creates (or promotes) the matching
plan task to "in progress", stops any running timer, creates a new log entry,
and starts the tick interval. Uses overrideTs as the entry start time so
elapsed time is counted from the scheduled start, not wall-clock now.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
blockId |
string | ID of the time block to start. |
|
overrideTs |
number |
<optional> |
Optional explicit start timestamp (ms). Defaults to |
- Source:
tickClock()
Updates the live clock display (date, time, ISO week), the time-block "now" line, and block notifications. Also detects midnight rollover: carries unfinished plan tasks to the new day and re-renders the UI.
- Source:
tickTimer()
Called every second by the timer interval. Updates the timer bar text, the live time-block element, the tab title/favicon, the bar colour, and checks whether a chime should fire. Also refreshes the focus-mode overlay when it is open. Errors are caught and logged so a single bad tick cannot stop the interval.
- Source:
tidyStaleEpics() → {number}
Archives every epic with no log entry and no board task in the last EPIC_STALE_DAYS days, after confirming the list with the user. Archived epics drop out of the pickers but keep their record, so historical entries still resolve their label and colour through getCat().
Impure by design: it reads the module-level state the tag row already owns (categories, entries, planTasks, selectedTag) and persists through save(). The decision itself lives in findStaleCategories (pure-fns-epics.js).
- Source:
Returns:
How many epics were archived (0 when none were stale or the user cancelled).
- Type
- number
timeToSlot(hhmm, m2opt) → {number}
Converts a time value to a 0-based slot index relative to TB_START.
Accepts either an "HH:MM" string or two separate (hours, minutes) arguments.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
hhmm |
string | number | "HH:MM" string, or hours when |
|
m2 |
number |
<optional> |
Minutes (only when |
- Source:
Returns:
0-based slot index.
- Type
- number
timedEntriesForDay(dateKey) → {Array.<Object>}
Returns the given day's entries that have a positive tracked duration and are not cancelled — the same filter the plaintext export uses.
Parameters:
| Name | Type | Description |
|---|---|---|
dateKey |
string | Day to read, |
- Source:
Returns:
Timed entries for that day.
- Type
- Array.<Object>
toTimeInput(ts) → {string}
Converts a Unix timestamp (ms) to an HH:MM string suitable for an
<input type="time"> value.
Parameters:
| Name | Type | Description |
|---|---|---|
ts |
number | Unix timestamp in milliseconds. |
- Source:
Returns:
Local time formatted as "HH:MM".
- Type
- string
toggleViewLocation() → {void}
Toggles the currently viewed day to the next location (Remote ↔ Office), persists it, logs the decision, and re-renders the header button.
- Source:
Returns:
- Type
- void
totalISOWeeks(year) → {number}
Returns the total number of ISO weeks in a given year (52 or 53). Uses the fact that Dec 28 is always in the last ISO week.
Parameters:
| Name | Type | Description |
|---|---|---|
year |
number | The full 4-digit year. |
- Source:
Returns:
52 or 53.
- Type
- number
trackerDayStatus(tracker, dateKey) → {'hit'|'partial'|'miss'}
Returns 'hit' | 'partial' | 'miss' for a given tracker on a given day.
Parameters:
| Name | Type | Description |
|---|---|---|
tracker |
Object | Tracker object with tags and targetMinutes. |
dateKey |
string | YYYY-MM-DD date key. |
- Source:
Returns:
- Type
- 'hit' | 'partial' | 'miss'
trackerStreak(tracker) → {number}
Calculates the current consecutive "hit" streak for a tracker, ending today. Walks backwards day-by-day (up to 60 days) and stops at the first non-hit day.
Parameters:
| Name | Type | Description |
|---|---|---|
tracker |
Object | Tracker object with |
- Source:
Returns:
Number of consecutive hit days ending today.
- Type
- number
trapFocusInOverlay(overlayEl, e) → {void}
Keeps keyboard focus inside overlayEl while it is open (WCAG 2.1.2).
Wraps forward from the last focusable element back to the first (Tab) and
backward from the first to the last (Shift-Tab). Call from the overlay's
keydown handler whenever e.key === 'Tab'.
Parameters:
| Name | Type | Description |
|---|---|---|
overlayEl |
HTMLElement | The open overlay container. |
e |
KeyboardEvent | The Tab keydown event. |
- Source:
Returns:
- Type
- void
triggerPortableDeploy()
Fire-and-forget portable deploy: calls POST /api/portable-deploy to trigger
the PowerShell build script, then displays a transient top-right toast with the
result (success, failure, or server unreachable). Never throws.
- Source:
tsToMins(ts) → {number}
Converts a Unix timestamp (ms) to minutes-from-midnight in local time.
Parameters:
| Name | Type | Description |
|---|---|---|
ts |
number |
- Source:
Returns:
- Type
- number
updateBoardLive() → {void}
Updates the #boardLive running-task strip above the board tabs.
Shows the strip (with task title, category, and elapsed clock) when a
non-paused timer is active; hides it otherwise.
Called from renderPlan() after every render cycle.
- Source:
Returns:
- Type
- void
updateLiveBlock()
Updates the live time-block element in the time-block view to reflect the current elapsed time of the active timer. No-ops if the live block element or the active timer's entry cannot be found.
- Source:
updatePomoDisplay()
Refreshes the pomodoro timer display: updates the countdown text, redraws segments, sets the state-modifier class on the pomo-body, and updates button labels and status text to reflect the current state.
- Source:
updatePomoTaskLabel()
Updates #pomoTaskLabel (composer column) with the text of the currently
running timer entry. Clears the label when no timer is active.
- Source:
updateTabAndFavicon()
Synchronises the browser tab title and favicon with the current timer state. Adds a ▶/⏸/🔴 prefix and shows the elapsed time and task name in the title. Switches to hyperfocus state (red) after HYPERFOCUS_MINS minutes for non-meeting tasks.
- Source:
updateTimerArc(elapsedMs)
Updates the SVG arc on the timer circle to reflect elapsed time relative to the hyperfocus threshold. The arc fills 0→100% of the circle circumference (2πr ≈ 150.8 px for r=24) as elapsed time goes from 0 to HYPERFOCUS_MINS.
Parameters:
| Name | Type | Description |
|---|---|---|
elapsedMs |
number | Elapsed time in milliseconds. |
- Source:
updateTimerBar()
Shows or hides the timer bar and updates the pause/resume button label. Also enables or disables the "make it interesting" hook button.
- Source:
updateTimerBtn(running)
Updates the main start-timer button's label and disabled state.
Parameters:
| Name | Type | Description |
|---|---|---|
running |
boolean | True if a timer is currently active. |
- Source:
validBlock(b) → {boolean}
Returns true if b is a well-formed timeblock record.
Parameters:
| Name | Type | Description |
|---|---|---|
b |
* | Candidate value parsed from JSON. |
- Source:
Returns:
True if the timeblock is well-formed.
- Type
- boolean
validCalendarMeeting(meeting) → {boolean}
Returns true if meeting is a well-formed Outlook calendar event object
as returned by the PowerShell /api/calendar endpoint.
Required fields: subject (string), start (string), end (string).
Optional fields (joinUrl, account) are not validated here — their
absence is handled gracefully by renderCalStrip.
Parameters:
| Name | Type | Description |
|---|---|---|
meeting |
* | Candidate meeting object from the calendar API response. |
- Source:
Returns:
True if the meeting object is well-formed.
- Type
- boolean
Example
validCalendarMeeting({ subject: 'Standup', start: '2026-05-28T09:00', end: '2026-05-28T09:30' })
// → true
validCalendarMeeting(null) // → false
validCalendarMeeting({ subject: 'x' }) // → false (missing start/end)
validCalendarMeeting({ subject: 42, start: '2026-05-28T09:00', end: '2026-05-28T09:30' })
// → false (subject not a string)
validCategory(c) → {boolean}
Returns true if c is a well-formed category object.
Parameters:
| Name | Type | Description |
|---|---|---|
c |
* | Candidate value parsed from JSON. |
- Source:
Returns:
True if the category is well-formed.
- Type
- boolean
validEntry(e) → {boolean}
Returns true if e is a well-formed work-log entry safe to load from localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
e |
* | Candidate value parsed from JSON. |
- Source:
Returns:
True if the entry is well-formed.
- Type
- boolean
Example
validEntry({ id: '1', text: 'Write report', ts: 1234567890, date: '2026-05-25' }) // → true
validEntry(null) // → false
validEntry({ id: 1, text: 'x', ts: 0, date: '2026-05-25' }) // → false (numeric id)
validEntry({ id: '1', text: 'x', ts: 0, date: '25-05-2026' }) // → false (wrong date format)
validEntry({ id: '1', text: 'x', ts: 0, date: '2026-05-25', link: 42 }) // → false (non-string link)
validJiraCsvRow(row) → {boolean}
Returns true if row — a single object produced by parseCSV() — contains
at least the key and summary columns that jiraParseAndRender() expects.
The Jira CSV export uses several possible column names for the same field
(matching the lenient lookup in jiraParseAndRender):
- Key column:
Issue key,Key, orIssue Key - Summary column:
Summaryorsummary
A row that has neither column set (e.g. from a semicolon-delimited file parsed as single-column rows) fails validation and triggers a warning.
Parameters:
| Name | Type | Description |
|---|---|---|
row |
* | Single row object from |
- Source:
Returns:
True if the row has the key and summary fields Jira import needs.
- Type
- boolean
Example
validJiraCsvRow({ 'Issue key': 'AITO-1', Summary: 'Fix login bug', Status: 'Open' })
// → true
validJiraCsvRow({ Key: 'PROJ-2', Summary: 'Add dark mode', Status: 'To Do' })
// → true
validJiraCsvRow({}) // → false (no key or summary)
validJiraCsvRow({ 'Issue key': 'AITO-1' }) // → false (missing summary)
validJiraCsvRow({ Summary: 'Fix login bug' }) // → false (missing key)
validPlanTask(t) → {boolean}
Returns true if t is a well-formed plan task with a recognised status value.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
* | Candidate value parsed from JSON. |
- Source:
Returns:
True if the plan task is well-formed.
- Type
- boolean
Example
validPlanTask({ id: 'pk1', text: 'Build form', date: '2026-05-25', status: 'todo' }) // → true
validPlanTask({ id: 'pk1', text: 'x', date: '2026-05-25', status: 'finished' }) // → false (unknown status)
validPlanTask(null) // → false
validPomoEntry(e) → {boolean}
Returns true if e is a valid Pomodoro session log entry.
Parameters:
| Name | Type | Description |
|---|---|---|
e |
* | Candidate value. |
- Source:
Returns:
True if the Pomodoro entry is well-formed.
- Type
- boolean
validTimer(t) → {boolean}
Returns true if t is a resumable timer state.
Handles both running (startTs is set) and paused (paused=true, accumulatedMs is set) forms.
Parameters:
| Name | Type | Description |
|---|---|---|
t |
* | Candidate value parsed from JSON. |
- Source:
Returns:
True if the timer state is well-formed.
- Type
- boolean
Example
validTimer({ entryId: 'e1', startTs: 1234567890 }) // → true (running)
validTimer({ entryId: 'e1', paused: true, accumulatedMs: 900000 }) // → true (paused)
validTimer({ entryId: 'e1' }) // → false (neither running nor paused)
validTimer(null) // → false
validWeatherResponse(data) → {boolean}
Returns true if data is a well-formed Open-Meteo forecast response
containing the fields that fetchWeather() reads.
Required shape:
data.current.temperature_2m— numberdata.current.weather_code— numberdata.hourly.time— arraydata.hourly.precipitation_probability— array
The daily block is optional (used only when present).
Parameters:
| Name | Type | Description |
|---|---|---|
data |
* | Value parsed from the Open-Meteo JSON response. |
- Source:
Returns:
True if the response is a usable forecast object.
- Type
- boolean
Example
validWeatherResponse({
current: { temperature_2m: 15, weather_code: 3 },
hourly: { time: ['2026-05-28T00:00'], precipitation_probability: [10] },
}) // → true
validWeatherResponse(null) // → false
validWeatherResponse({ current: {} }) // → false (missing hourly)
validWeatherResponse({ hourly: { time: [], precipitation_probability: [] } })
// → false (missing current)
validateBackupFile(backup) → {Object}
Validates a parsed JSON backup object created by exportBackup().
Separated from the import flow so the validation logic can be unit-tested
without any browser APIs. importBackup() calls this before writing to
localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
backup |
* | Parsed backup object (typically from |
- Source:
Returns:
{ valid: true } when the backup is usable;
{ valid: false, error: string } with a human-readable reason otherwise.
- Type
- Object
Example
validateBackupFile({ version: '1', entries: [], categories: [], planTasks: [] })
// → { valid: true }
validateBackupFile(null)
// → { valid: false, error: 'Not a valid backup object.' }
validateBackupFile({ version: '2', entries: [], categories: [], planTasks: [] })
// → { valid: false, error: 'Unrecognised backup version "2"...' }
validateBackupFile({ version: '1', entries: [], categories: [] })
// → { valid: false, error: '...missing required field "planTasks".' }
viewEntries() → {Array.<object>}
Returns entries for the currently viewed date, sorted newest-first by
start time (ts) — not by insertion order. This keeps retroactively
added entries (e.g. filling in a missed morning slot after the day is
already logged) positioned correctly rather than jumping to the top or
staying at the bottom based on when they were typed in.
- Source:
Returns:
- Type
- Array.<object>
visibleMeetings(meetings) → {Array.<Object>}
Drops the meetings the user has hidden today.
Parameters:
| Name | Type | Description |
|---|---|---|
meetings |
Array.<Object> | Validated meetings from the calendar API. |
- Source:
Returns:
The meetings still to be shown.
- Type
- Array.<Object>
warn(msg, dataopt)
Emits a warning — something unexpected but non-fatal.
Parameters:
| Name | Type | Attributes | Description |
|---|---|---|---|
msg |
string | Human-readable message. |
|
data |
* |
<optional> |
Optional value to attach to the log line. |
weatherEmoji(code) → {string}
Maps a WMO weather interpretation code to a representative emoji.
Parameters:
| Name | Type | Description |
|---|---|---|
code |
number | WMO weather code (0 = clear sky, 95+ = thunderstorm). |
- Source:
Returns:
A single weather emoji character.
- Type
- string
weekRangeLabel(weekStart) → {string}
Formats a week-start timestamp as a "Mon DD Mon – Sun DD Mon" range label.
Parameters:
| Name | Type | Description |
|---|---|---|
weekStart |
number | Monday 00:00 local time, in ms. |
- Source:
Returns:
The formatted range.
- Type
- string
writeCollapseState(sectionId, collapsed) → {void}
Writes the collapsed state for a section to localStorage.
Parameters:
| Name | Type | Description |
|---|---|---|
sectionId |
string | The section element's |
collapsed |
boolean | Whether the section is now collapsed. |
- Source:
Returns:
- Type
- void
writeExportFile(subfolder, filename, blob) → {Promise.<void>}
Writes a Blob to subfolder/filename inside the user's chosen FSA directory.
Creates the subfolder if it does not exist. Falls back to a browser <a>
download if the FSA handle is missing or permission is not granted.
Parameters:
| Name | Type | Description |
|---|---|---|
subfolder |
string | Name of the subfolder to write into. |
filename |
string | Name of the file to create or overwrite. |
blob |
Blob | File content. |
- Source:
Returns:
- Type
- Promise.<void>
Type Definitions
EpicCategory
An epic (category) record as persisted in wl_cats_v1.
Type:
- Object
Properties:
| Name | Type | Attributes | Description |
|---|---|---|---|
id |
string | Unique epic ID. |
|
label |
string | Display name. |
|
color |
string |
<optional> |
CSS colour. |
archived |
boolean |
<optional> |
|
billable |
boolean |
<optional> |
Default billable flag; absent or |
- Source:
EpicUsageRecord
A log entry or board task, seen only as evidence that an epic is in use.
Type:
- Object
Properties:
| Name | Type | Attributes | Description |
|---|---|---|---|
tag |
string |
<optional> |
Epic ID this record is tagged with, if any. |
date |
string |
<optional> |
ISO date (YYYY-MM-DD) the record belongs to, if any. |
- Source:
EpicUsageSources
The two record collections scanned for evidence that an epic is in use.
Type:
- Object
Properties:
| Name | Type | Attributes | Description |
|---|---|---|---|
entries |
Array.<EpicUsageRecord> |
<optional> |
Log entries to scan. |
planTasks |
Array.<EpicUsageRecord> |
<optional> |
Board tasks to scan. |
- Source:
Migration
Describes one key-rename migration.
Type:
- Object
- Source:
PomoSessionEntry
Type:
- Object
Properties:
| Name | Type | Description |
|---|---|---|
ts |
number | Unix timestamp ms when the session was logged. |
mins |
number | Duration of the session in minutes. |
task |
string | null | Task name linked to the session, or null if none. |
- Source:
PomoSessionEntry
Type:
- Object
Properties:
| Name | Type | Description |
|---|---|---|
ts |
number | Unix timestamp ms when the session was logged. |
mins |
number | Duration of the session in minutes. |
task |
string | null | Task name linked to the session, or null if none. |
- Source:
QuickCaptureEntry
A time-log entry as read by the quick-capture task list.
Type:
- Object
Properties:
| Name | Type | Description |
|---|---|---|
id |
string | Unique entry id. |
text |
string | Entry description. |
tag |
string | Category id. |
date |
string | Entry date as YYYY-MM-DD. |
- Source:
QuickCaptureTask
A plan (kanban) task as read by the quick-capture task list.
Type:
- Object
Properties:
| Name | Type | Attributes | Description |
|---|---|---|---|
text |
string | Task description. |
|
tag |
string | Category id. |
|
date |
string | Task date as YYYY-MM-DD. |
|
status |
string | Board status, e.g. 'todo', 'inprogress', 'done'. |
|
_migrated |
boolean |
<optional> |
True when the task was carried to a later day. |
- Source:
RetiredKey
Describes one localStorage key whose owning feature has been removed.
Type:
- Object
- Source: