Commit graph

19950 commits

Author SHA1 Message Date
Johannes Millan
9c7cb7c3eb test(reminders): move TestBed.inject after overrideProvider
Calling TestBed.inject(MockStore) in beforeEach instantiated the test
module, so the subsequent TestBed.overrideProvider(MAT_DIALOG_DATA) in
createComponent threw "Cannot override provider when the test module
has already been instantiated" across all 7 destroy-cleanup specs.

Move the store inject and dispatch spy into createComponent, after the
override runs, so TestBed stays un-instantiated until the per-test
MAT_DIALOG_DATA value is registered.
2026-04-21 22:02:03 +02:00
Johannes Millan
30550efa34 chore(deps): sync plugin lockfiles after vite-plugin bump to ^6.4.2
packages/vite-plugin/package.json was bumped to vite ^6.4.2 in
4b9bd23e56 but dependent plugin lockfiles still referenced ^6.0.0.
npm install during the e2e build brought them in sync and hoisted
vitest's nested esbuild binaries, dropping ~500 redundant entries.
2026-04-21 21:50:53 +02:00
Johannes Millan
ff523fd4dd refactor(reminders): address code review feedback for deadline fixes
- Document intent of editReminder cancel path: when user cancels the
  inner schedule dialog, the outer dialog closes and the destroy handler
  clears deadlineRemindAt (same as ESC). Prevents the worker from
  re-firing the past-due reminder.
- Replace page.waitForTimeout with expect().not.toBeVisible({timeout}) in
  the deadline ESC e2e test so it fails fast if the dialog reappears and
  complies with the "no waitForTimeout" rule in e2e/CLAUDE.md.
- Capture realTodayStr per test in is-deadline-approaching spec to close
  the (narrow) midnight-rollover flake window.
2026-04-21 21:50:52 +02:00
Johannes Millan
c9694cc58f fix(tasks): make amber deadline badge actually render
.deadline-btn.mat-accent mat-icon needed the same \!important override
as .mat-warn mat-icon to beat Material's icon color variable. Without
it, setting color="accent" on the button left the icon in its default
neutral color.
2026-04-21 21:50:52 +02:00
Johannes Millan
944d003775 feat(tasks): color deadline badge amber within 2 days of the deadline
The deadline feature spec (issue #4328) promised green/amber/red urgency
tiers, but only the overdue-red state was implemented. Preview testers
flagged the missing intermediate tier.

Adds isDeadlineApproaching(task, todayStr) returning true when an active,
non-overdue deadline is within two calendar days. Task row badge now
shows 'warn' (red) when overdue, 'accent' (amber) when approaching, and
default color otherwise.
2026-04-21 21:50:52 +02:00
Johannes Millan
2db074d8e9 fix(reminders): clear unhandled deadline reminders on dialog close
Without this, ESC/backdrop dismissal left deadlineRemindAt in the store,
so the reminder worker re-fired on its next 10s tick and the dialog
reopened indefinitely (reported in #4328 preview feedback).

ngOnDestroy now dispatches clearDeadlineReminder for any deadline
reminder shown in the session that was not explicitly handled. Bulk
handlers (markSingleAsDone, markAllTasksAsDone, addAllToToday) now
populate _dismissedReminderIds so destroy is a no-op for them.

Adds 7 unit tests and a Playwright e2e regression test that fails on
the unpatched component and passes with the fix.
2026-04-21 21:50:52 +02:00
Johannes Millan
86645964fd test(schedule): trigger recompute for week-view headerTitle spec
Same-value signal.set on selectedTimeView was a no-op, leaving the
cached headerTitle from beforeEach (with Jan dates and the untranslated
key). Setting _selectedDate invalidates daysToShow so the April mock
return value and the newly loaded WEEK_NR translation are picked up.
2026-04-21 21:50:52 +02:00
Johannes Millan
80c6caf403 test(schedule): align component specs with new prev-nav-at-today guard
Update tests that assumed backward navigation from today still worked and
that the mocked day range was ignored by isViewingToday. Use future dates
and override getDaysToShow where needed so the guard doesn't short-circuit,
and load the WEEK_NR translation for the week-view headerTitle test.
2026-04-21 21:50:52 +02:00
Johannes Millan
960d269ca2 fix(schedule): deduplicate @for track key for split event segments
Split projections (RepeatProjectionSplit, SplitTaskContinued, and related
types) share the underlying task/repeat-cfg id across their visual segments,
producing NG0955 duplicate-key warnings in the week-view grid. Combine id,
plannedForDay, and startHours so each segment has a unique track key and
Angular can reconcile views cleanly.
2026-04-21 21:50:52 +02:00
Johannes Millan
28daf519fb feat(schedule): add week number and date range to nav header
Show "Week N · start – end" in week view and "Month YYYY" in month view,
right-aligned in the schedule nav bar. Move the week/month view toggle
out of main-header into the left of the schedule bar. Replace the "Now"
text button with a circle icon next to the chevrons, and disable prev
navigation when today is already in the displayed range.

- ISO 8601 week numbers via existing getWeekNumber util
- Parse day strings via parseDbDateStr to avoid UTC-midnight TZ skew
- Reuse existing T.F.WORKLOG.CMP.WEEK_NR / T.F.TODAY_TAG_TITLE keys
- Drop the duplicate month-title inside schedule-month; the shared nav
  now owns the label for both views
2026-04-21 21:50:52 +02:00
Johannes Millan
add8a511f9 style(main-header): size sync icon to match other header icons
The sync button was pinned to 28px while every other header icon
(add task, panel toggles, etc.) sits at 20px, making it visually
oversized. Drop the sync-btn-specific override so it inherits the
shared 20px rule, and switch the general selector to exclude only
the small .sync-state-ico overlay badge (still 15px with absolute
positioning).
2026-04-21 21:50:52 +02:00
Johannes Millan
00d97bffbe fix(splash): keep productivity-tip heading readable on dark theme
The global :where(h1..h6):not(...):not(...):not(...) rule in page.scss
pins h2 color to --text-color-most-intense, which defaults to black on
:root and only flips to white once body.isDarkTheme is added at
bootstrap. During the pre-bootstrap splash the heading was therefore
black on the dark body background. Target the heading by id so the
selector clears the page rule's (0,3,0) specificity and let it inherit
the body color (already correct for both prefers-color-scheme modes).
2026-04-21 21:50:52 +02:00
Johannes Millan
d74a621d68 feat(main-header): hide play button on mobile unless tracking
On small screens the play button now only renders when a task is
actively tracked or a focus-mode session is running, freeing header
space when nothing is in progress.
2026-04-21 21:50:52 +02:00
Johannes Millan
79a618113a fix(calendar): surface clearer error when iCal URL returns HTML (#5355)
Revoked Office365 share links respond with a 302 → HTML "Object moved"
page. ICAL.parse() then threw a cryptic error that reached the user as
a generic "Calendar Provider Error" every ~2 hours.

Sniff the response at the parser boundary and throw a typed
NotIcalResponseError so the service can branch to a dedicated snackbar
message pointing the user at the likely cause (expired/revoked link).

Also replace the pre-existing `throw new Error(err)` in the forward-error
path with `throw err` so the typed error identity survives for upstream
consumers.
2026-04-21 21:50:52 +02:00
Johannes Millan
422741f33f fix(focus-mode): preserve tracked task on header pause/resume (#6726)
Header play/pause button chained two unsetCurrentTask dispatches
(toggleStart → unsetCurrentTask → syncTrackingStopToSession$ →
pauseFocusSession → syncSessionPauseToTracking$ → unsetCurrentTask).
The second call ran with currentTaskId already null, so the reducer
captured null into lastCurrentTaskId, causing the next toggleStart
to fall back to the top task instead of the user's choice.

Fix at two layers:
- Effect: guard syncSessionPauseToTracking$ with currentTaskId$ so it
  skips the redundant dispatch.
- Reducer: make unsetCurrentTask idempotent for lastCurrentTaskId when
  currentTaskId is already null, defending the invariant against any
  similar chain.
2026-04-21 21:50:52 +02:00
Johannes Millan
9aba039b59 test(e2e): tighten backup-export selector for renamed privacy button
The exportBackup helper used `file-imex button:has-text("Export")`, which
matched both the backup Export button and the newly-text-labeled
"Export Data (anonymized)" privacy button introduced in #7141. Playwright
strict mode on the subsequent click() threw, causing both non-skipped
tests in this file to fail. Switch to an exact name match so only the
intended button is targeted.
2026-04-21 21:50:52 +02:00
Johannes Millan
80976c32c1 ci(electron): wire node --test for electron/*.test.cjs
Follow-up to #7297: the simple-store regression test only ran via
local invocation. Add a test:electron npm script and run it in the
CI Tests job so simple-store fixes stay regression-guarded.
2026-04-21 21:50:52 +02:00
Johannes Millan
e88af693a0 fix(sidenav): write input-intent body classes on hybrid device init
On hybrid devices (e.g. Windows 11 touchscreen laptops with a mouse),
GlobalThemeService skips the static isMousePrimary/isTouchPrimary body
class assignment and defers to InputIntentService. But _setIntent's
equality guard short-circuited the mouse->mouse initial call, so neither
class was ever written until the first pointer event. That broke the
sidebar CSS that hides .additional-btn on mouse-primary and repositions
.task-count inline on touch-primary, causing the project counters to
overlap the kebab menu (#7132).
2026-04-21 21:50:52 +02:00
Spyros Seimenis
315ae24e37
Fix/gitea issue id and done sync (#7288)
* fix(issue/gitea): set issueId and isDone in getAddTaskData (related #1609)

Mirrors the GitLab provider. Two effects:

- isDone: when the upstream Forgejo/Gitea issue closes, the SP task
  flips to done on the next poll (BaseIssueProviderService.
  getFreshDataForIssueTask spreads getAddTaskData into taskChanges, so
  closed-state propagates to existing tasks too (not just new ones)).

- issueId: tasks created via auto-import now actually carry the issue
  id, so checkAndImportNewIssuesToBacklogForProject's dedup filter
  (allExistingIssueIds.includes(issue.id)) starts working. Previously
  every poll surfaced every Gitea issue as "new", because no stored
  task had an issueId to compare against.

Refs #1609, #1524 (closed-stale GitHub/GitLab requests for the same
behavior on those providers).

* fix(issue): skip default dueDay when adding issue to backlog

addTaskFromIssue defaulted every imported task's dueDay to today, so
even tasks intentionally added to a project's Backlog showed up in the
Today tab, defeating the purpose of the Backlog and surprising users
who configured providers to auto-import into Backlog.

Skip the default when isAddToBacklog is true. The provider's own
getAddTaskData (and explicit additional fields) can still set dueDay
if they want to.

* fix(issue): don't yank backlog tasks to Today on re-import (related #4324)

_checkAndHandleIssueAlreadyAdded unconditionally calls
moveTaskToTodayList whenever the existing task's projectId matches the
active work context. For users who auto-import provider issues into a
project Backlog, every poll that re-surfaces an already-imported issue
promotes the task out of Backlog into Today, flooding Today with
tasks the user consciously parked.

Guard the move: if the existing task is already in the project's
backlogTaskIds, treat it as already handled and return early.
The snackbar (FOUND_MOVE_FROM_BACKLOG) is also skipped, there's
nothing to announce when nothing moved.

Refs #4324, #3933 (closed-stale Gitea/GitLab reports of the same
symptom).

* fix(issue/gitea): use issue.number for issueId to prevent polling 404s

Gitea's getById$ URL uses the per-repo issue number, and search results
are already normalized via mapGiteaIssueIdToIssueNumber so stored tasks
carry issueId = issue.number. Using issue.id in getAddTaskData (the
polling path) overwrote that with the global DB id on the first
issue-updated poll, causing subsequent getById$ calls to 404.

* feat(issue): show 'already exists' snack when re-adding a backlog task

Manually re-picking a backlog-resident issue from the add-task search
used to silently no-op after the backlog early-return. Mirror the
existing TASK_ALREADY_EXISTS snack with a GO_TO_TASK action so the user
gets feedback and a way to jump to the task.

* test(issue): cover isAddToBacklog and backlog re-import paths

- isAddToBacklog=true does not set dueDay; default behavior still does.
- Existing backlog task is not pulled to Today on re-import.
- Existing Today-side task in the same project is still pulled in and
  surfaces the FOUND_MOVE_FROM_BACKLOG snack.
2026-04-21 21:00:23 +02:00
Aki
f0b4be592e
fix(github-plugin): enable github advanced search to make queries including "OR" or "AND" logic operators work (#7304) 2026-04-21 15:56:29 +02:00
Johannes Millan
e571cc2433
Feat/issue 7300 4d6d23 (#7303)
* fix(search): use getElementById to resolve focus target

querySelector(`#t-${taskId}`) throws SyntaxError when taskId starts
with `-` or a digit (both valid nanoid outputs). Swap for getElementById
plus container.contains() to keep the scope check.

* fix(sync): verify WebDAV uploads to detect silent truncation

Closes #7300

WebDAV servers (incl. Nextcloud) don't enforce upload integrity, so a
PUT cut short by flaky network or proxy leaves a truncated file on the
server. Every subsequent sync then fails to decompress the same corrupt
bytes with the opaque browser error "compressed Input was truncated".

- WebdavApi.upload() now re-GETs the file after PUT and compares content
  hashes. The verification GET rejects empty and HTML bodies (login
  pages / proxy errors) so transport failures aren't misreported as
  corruption, and sends Cache-Control: no-cache to defeat intermediate
  caches. On hash mismatch it throws RemoteFileChangedUnexpectedly —
  truncation and concurrent writes are indistinguishable from the
  mismatch alone, so both flow through the adapter's existing
  re-download + retry path. A persistently truncated remote is then
  surfaced through DecompressError on the next cycle, where the guidance
  is actionable.
- DecompressError translates truncation messages into actionable
  recovery guidance referencing FILE_BASED_SYNC_CONSTANTS.SYNC_FILE.
- compressWithGzip/compressWithGzipToString now await write().then(close)
  via Promise.all, matching the decompression path and eliminating a
  latent race on older Android WebViews.

* refactor(sync): drop SyncDataCorruptedError re-export to break import cycle

Review of the #7300 fix flagged that sync-errors.ts now imports
FILE_BASED_SYNC_CONSTANTS from file-based-sync.types.ts, while
file-based-sync.types.ts re-exports SyncDataCorruptedError back from
sync-errors.ts — a structural circular import that works today only
because the references are lazy, and would break if a future refactor
made either eager.

The re-export's own comment flagged it as backward-compat-only. Drop it
and redirect the two remaining consumers to import directly from
sync-errors, as the comment already asked.
2026-04-21 15:26:34 +02:00
Johannes Millan
bffbbaed5b docs(research): §18.7 — source-level mechanism for appendSwitch vs argv divergence
Trace the root cause to Electron's init order: Ozone platform selection
fires in C++ PreEarlyInitialization() and the chosen platform is
memoized in ui/ozone/platform_selection.cc's g_selected_platform
static. Any app.commandLine.appendSwitch() from main.js runs in
PostEarlyInitialization() — after the memoization, so the write is
correct but the read has already happened. Primary source: electron
PR #48301 diff + Chromium ui/ozone/platform_selection.cc.

This confirms the argv wrapper is structurally the only fix available
from outside the Electron binary. Also documents why the two
lower-friction alternatives don't work:

- ELECTRON_OZONE_PLATFORM_HINT env var was removed as dead code in
  Electron 39 (PR #47983).
- Setting XDG_SESSION_TYPE=x11 via snapcraft.yaml environment: would
  work for Ozone but would fool IdleTimeHandler, silently breaking
  GNOME Wayland idle detection.

Confidence in the mechanism hypothesis is raised from ~40% to ~85%.
Residual unknown: whether GPU child-process CommandLine inheritance
exposes a late appendSwitch to subsystems other than Ozone. Not
verified from source; does not change the shipping recommendation.

Related: issue #7270.
2026-04-21 15:03:42 +02:00
Johannes Millan
297aeb30ab fix(electron): harden Snap+Wayland argv wrapper after multi-review
Address findings from code-reviewer + debugger-assistant +
refactor-specialist multi-review on 8e1c47ebc2:

- afterPack: read wrapper source before rename; on writeFile failure,
  roll the rename back so a broken intermediate state can't ship a
  package with no launcher. Strengthen idempotency check to require a
  shebang at binPath (both files alone no longer counts as "installed").
- wrapper: gate the X11 injection on \$SNAP_NAME = "superproductivity",
  not just \$SNAP set. An xdg-open from a sibling snap (e.g. Firefox)
  leaks \$SNAP into the child env, which previously would have silently
  forced XWayland on .deb/.rpm users. Derive BIN_DIR from \$SNAP
  directly when we are our snap, avoiding fragile \$0 resolution
  through snapd's wrapper chain. Stop argv scanning at -- so positional
  args that resemble --ozone-platform aren't misread as a user override.
- app-control: point app.relaunch() execPath at the sibling shell
  wrapper instead of the default process.execPath (which is the renamed
  ELF and would bypass the flag injection on a relaunched Snap+Wayland
  instance).
- Move build/snap-wrapper.sh -> build/linux/snap-wrapper.sh to match
  the existing build/linux/ layout convention.
- Update research doc §18 to reflect the tightened predicate and the
  relaunch fix.
2026-04-21 15:03:42 +02:00
Johannes Millan
7f503c9bf0 fix(electron): inject ozone-platform=x11 via argv wrapper for Snap+Wayland
Field reports on issue #7270 (v18.2.4 / v18.2.5) show that
app.commandLine.appendSwitch('ozone-platform','x11') is not equivalent
to the CLI flag on Snap+Wayland: Chromium's Ozone init dlopens
libEGL/libgbm on the core22 Mesa path before the switch is honored and
segfaults under host-vs-snap Mesa ABI drift, while the same flag on
argv consistently works.

Install an afterPack hook that renames the Electron binary and drops a
shell wrapper at the original name. The wrapper injects the flag into
argv only when \$SNAP is set AND the session is Wayland — non-Snap
targets (AppImage, .deb, .rpm) and X11 sessions pass through untouched.
Matches the mechanism used by snapcrafters/signal-desktop and
snapcrafters/mattermost-desktop.

See docs/research/snap-wayland-gpu-fix-research.md §18 for the full
evidence trail, drawbacks, validation plan, and removal conditions.
2026-04-21 15:03:42 +02:00
Johannes Millan
78f64508db feat(sync-server): add active-users-quick monitor command
Avoids the operations table entirely (sync_devices + users only) so it
returns in milliseconds even when operations grows past 1 GB. Useful when
the standard active-users command is too slow on large prod DBs.
2026-04-21 15:03:42 +02:00
Johannes Millan
0f99f38ca9 fix(window): persist maximized state across tray-quit (#7276)
electron-window-state reads isMaximized() on an already-hidden window in
its `closed` handler, which returns false (electron#27838). Patch the
persisted state file in will-quit using our own wasMaximizedBeforeHide
flag, which is kept accurate by the maximize/unmaximize event listeners.
2026-04-21 15:03:41 +02:00
Johannes Millan
7fa03c489e fix(github-plugin): encode parentheses in search queries (#4913)
encodeURIComponent leaves ( and ) intact per RFC 3986, but GitHub's
search API treats them as grouping operators that must be percent-encoded,
returning HTTP 422 on queries like "(author:@me OR assignee:@me)".

The in-repo provider was fixed in 960330dd56 but the fix didn't carry
over when GitHub moved to a plugin. Reapply via a small encodeGithubQuery
helper used at both call sites.
2026-04-21 15:03:41 +02:00
Johannes Millan
0d5a562aba test(sync-server): freeze clock in 1ms-over-drift clamp test
The "just 1ms over max clock drift boundary" test captured Date.now()
once in the test and again inside uploadOps. If the second sample
advanced by >=1ms, the op was within the drift window from the
service's perspective, clamping never fired, and toBeLessThan failed
with equal values.

Freeze time with vi.useFakeTimers/setSystemTime so both samples
match, and tighten the assertion to the exact clamped value.
2026-04-21 15:03:41 +02:00
Johannes Millan
798ad04eab fix(search): use getElementById to resolve focus target
querySelector(`#t-${taskId}`) throws SyntaxError when taskId starts
with `-` or a digit (both valid nanoid outputs). Swap for getElementById
plus container.contains() to keep the scope check.
2026-04-21 15:03:41 +02:00
aakhter
453d980a41
fix(electron): harden simple store writes (#7297)
* fix: harden electron simple store writes

* fix(electron): harden simple store writes

---------

Co-authored-by: Aamer Akhter <aamer_akhter@users.noreply.bitbucket.org>
2026-04-21 15:03:30 +02:00
novikov1337danil
1abb15b804
refactor(backup): unify backup filename format (#7141)
* refactor(backup): implement consistent timestamp for backup filenames

* refactor(i18n): rename PRIVACY_EXPORT key to PRIVACY_EXPORT_DESCRIPTION

* refactor(i18n): add PRIVACY_EXPORT key for data export in multiple languages

* refactor(backup): standardize backup filename prefix

* refactor(backup): update backup filename format to use underscore separator

* refactor(i18n): update translations

* refactor(backup): move getBackupTimestamp utility to a shared location for consistency

* test(backup): add unit tests for getBackupTimestamp utility

* fix(e2e): move MIGRATION_BACKUP_PREFIX to migration.const

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* refactor(tests): move jasmine.clock().install() to beforeEach for consistency

* refactor(backup): consolidate backup filename constants and update imports

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-21 14:09:09 +02:00
aakhter
9fa09b683f
chore(debug): add opt-in search navigation traces (#7296)
Co-authored-by: Aamer Akhter <aamer_akhter@users.noreply.bitbucket.org>
2026-04-21 14:00:01 +02:00
aakhter
0794bbd279
feat(plugins): emit taskUpdate hook for project moves (#7302)
* Expose project moves through taskUpdate hook

* test: fix plugin hook task fixture typing

---------

Co-authored-by: Aamer Akhter <aamer_akhter@users.noreply.bitbucket.org>
2026-04-21 13:47:33 +02:00
aakhter
cef22fd57f
fix(search): scroll to selected task after search navigation (#7283)
* fix(search): wait for rendered task rows before scrolling

* test(layout): mock rendered task rows in focus specs

---------

Co-authored-by: Aamer Akhter <aamer_akhter@users.noreply.bitbucket.org>
2026-04-20 19:07:08 +02:00
Johannes Millan
fef34cff05 Merge branch 'feat/our-super-sync-monitoring-scripts-get-782dcc'
* feat/our-super-sync-monitoring-scripts-get-782dcc:
  fix(sync-server): avoid per-user correlated scans in monitor usage
2026-04-20 18:51:27 +02:00
Johannes Millan
62f9f9cfd4 fix(sync-server): avoid per-user correlated scans in monitor usage
The showUsage query ran two correlated subqueries over the full
operations table once per user, each calling pg_column_size(payload)
on every row, then sorted all users before LIMIT 20. On a 1.85 GB
operations table with 3.6k users this effectively never returned.

Aggregate per-user size and count in a single CTE pass, then join.
One sequential scan instead of scans × users.
2026-04-20 18:51:12 +02:00
Johannes Millan
ff0314e52d 18.2.5 2026-04-20 18:39:43 +02:00
Johannes Millan
18474d64e7 docs(research): add §17 — multi-agent review + 5-agent depth research
7-reviewer multi-review (6 Claude focus-agents + Codex CLI) over the
cherry-picked PR #7273 diff, plus 5 parallel research agents on Mesa
mechanism, Chromium flag behavior, peer-app field data, core22→core24
cost, and marker-timing semantics.

§17 captures: findings that changed the PR (now shipped in the
previous commit), disagreements worth noting (Codex W-C1 vs. three
independent flag-pair sources), validated decisions (APP_READY signal,
Snap config shape, novelty of the crash-loop mechanism), deferred
scope (pre-flight probe, core24 migration, forensic listener,
TOCTOU hardening), and confidence deltas.

Adds 14 new references (Mesa commit, libgbm ABI instability,
peer-app issues on Ubuntu 25.10, Canonical core24 migration docs,
electron-builder#8548, Obsidian launcher) tying each to the §17
claim it supports.
2026-04-20 18:35:31 +02:00
Johannes Millan
cbf3eb15d4 fix(electron): add GPU startup guard for confined Linux packages
Defense-in-depth against GPU init failures on Snap/Flatpak Linux where
the main process stays alive but the GPU process crashes at init and
the window never renders. Field data in #7270 (two post-v18.2.4
reports) shows this happens on Ubuntu 24.04+/25.10 regardless of GPU
vendor — the driver is core22 Mesa/libgbm drifted from the host Mesa.
See §12–§17 in docs/research/snap-wayland-gpu-fix-research.md.

Mechanism (electron/gpu-startup-guard.ts):

- Content-based crash marker in userData with {ts, electronVersion}.
  Written before app.whenReady() on confined Linux; cleared via
  IPC.APP_READY after Angular boot — not ready-to-show, which fires
  on blank/broken renderers too.
- Previous-crash detection: marker present AND recent (<5 min) AND
  matching Electron version. Staleness bound + version gating drop
  systemd-SIGKILL-mid-boot and post-upgrade-residue false-negatives.
- Env overrides SP_DISABLE_GPU=1 / SP_ENABLE_GPU=1 work on all
  platforms; auto-detection is Linux+Snap/Flatpak-only.
- Non-ENOENT fs errors logged at warn — a swallowed write-fail
  previously meant the guard could re-enter the loop with no
  diagnostic trail; a swallowed unlink-fail meant a successful boot
  could get permanently stuck in crash-recovery.

Fallback flag bundle (start-app.ts):
  --disable-gpu
  --disable-software-rasterizer
  --ozone-platform=x11

The pair matches Chromium's GPU integration tests' "no GPU process"
invariant; DisplayCompositor handles 2D in the browser process
without spawning a GPU child. app.disableHardwareAcceleration()
alone does NOT — verified against electron/electron#17180/#20702.
The extra --ozone-platform=x11 closes the Chromium 140+
browser-side Wayland/libgbm-dlopen gap on Flatpak (redundant with
the existing Snap X11 widening branch; last flag wins).

Novelty: R3 survey of VS Code, Slack, Insomnia, LosslessCut,
Obsidian flatpak, Firefox snap, and Canonical's gpu-2404-wrapper
found no peer Electron-snap implementing an equivalent reactive
crash-detection + auto-fallback. Prevailing patterns are manual
--ozone-platform=x11, proactive env-sniff, or do-nothing.

Review: §14–§15 multi-agent verification on the original PR #7273;
re-reviewed via 7-agent multi-review (6 Claude focus-agents + Codex
CLI) + 5 research agents here (§17). Live-tested with
SP_DISABLE_GPU=1 on a KDE/X11 dev host — window rendered normally
via DisplayCompositor.

Refs: #7270
2026-04-20 18:35:31 +02:00
Johannes Millan
9f447198bb docs(research): revive §12 PR #7273 — n=2 field data fires the revisit trigger
The "PR #7273 was closed" paragraph predates the two post-v18.2.4
field reports in §16. Updated to acknowledge the revisit condition
explicitly: reopen only if a real report came in that X11 widening
didn't rescue. §16 documents two such reports (Intel/Ubuntu 24.04 and
AMD/Ubuntu 25.10), both showing #7266's guard firing correctly and
still not rescuing the user. PR #7273's mechanism is no longer
speculative — it rescues the population #7266 provably does not.

Also wrap DISPLAY_COMPOSITOR in backticks on line 846 to prevent
prettier from parsing underscores as an italic span across lines.
2026-04-20 18:35:31 +02:00
Johannes Millan
6af2a6e0ac docs(research): update §16 with nekufa report (n=2, vendor-agnostic)
Second post-v18.2.4 field report (Ubuntu 25.10 + AMD Raphael) replicates
DerEchteKoschi's failure pattern (Ubuntu 24.04 + Intel Arrow Lake-P):
#7266's guard fires, MESA-LOADER fails on gbm/dri_gbm.so, GPU process
segfaults in a loop regardless of ozone platform. Confirms the tail is
driven by host-Mesa/core22-runtime drift, not GPU vendor or generation.

Changes in §16:
- Intro + Environments: switch to n=2 framing with per-reporter blocks.
- What the log proves: clarify shared pattern; flag nekufa's
  `--ozon-platform` typo (short-circuit in start-app.ts:73-75 only
  matches `--ozone-platform`, so the log reflects the programmatic path).
- Scope table: drop "Arrow Lake, newer" parenthetical — generic
  drift row covers any Ubuntu ≥ 24.04 with core22 mismatch.
- PR #7273 framing + core24 urgency cite n=2 evidence.
- Open question: note nekufa's typo disqualifies from CLI vs
  appendSwitch question.
- Actionable outcomes split reply-to-thread per reporter; add
  item 6 for separate Ctrl+Shift+X issue.
- Confidence: arch-specificity upgraded Medium → High.
- References: add both comment permalinks.

Prettier formatting applied.
2026-04-20 18:35:31 +02:00
Johannes Millan
b69190f4e2 style(plugins): equalize community plugin card heights
Stack card content in a flex column with consistent gap and clamp the
description to 3 lines so the grid no longer goes ragged when one
plugin ships a long shortDescription.
2026-04-20 17:36:07 +02:00
Johannes Millan
1a12867a72 feat(plugins): credit community plugin authors with stars and alpha sort
Add author/authorUrl/stars to community-plugins.json, sort entries
alphabetically by name, and restyle the section warning to match the
existing install-warning callout.
2026-04-20 17:36:07 +02:00
Johannes Millan
ca61a25352 fix(styles): drop dead body-level transition-duration overrides
The base body{} block was re-declaring --transition-duration-s (180ms)
and --transition-duration-l (400ms), shadowing the :root defaults
(150ms/375ms) for every element in the app regardless of theme
(body.isDarkTheme did not re-override them). That made the recent
token sweep silently drift some callers from 150ms to 180ms (+20%)
instead of the intended 1:1 swap, and left the light/dark experience
identical despite looking theme-specific.

Remove the three overrides — durations are now consistently the :root
defaults in both themes. Visible effect: task hover/select, done-toggle
SVG, and onboarding animations run about 30ms faster.

Update styling-guide.md to drop the obsolete "light theme overrides"
note and keep the bucket-rounding guidance.
2026-04-20 17:14:38 +02:00
Johannes Millan
2939f570e7 style: migrate hardcoded values to design tokens
Mechanical sweep replacing hardcoded values with documented design
tokens per docs/styling-guide.md. Transition durations align to the
nearest bucket (±15% tolerance); the other swaps are value-identical.

- Transitions/animations: done-toggle, heatmap, progress-circle, task,
  add-task-bar, main-header.
- Colors: #fff → var(--card-bg) on the task time-badge (visible in dark
  mode — badge now tracks surface color instead of staying pure white);
  #333 → var(--text-color) on work-view no-tasks label.
- Spacing: 4px/16px → var(--s-half)/var(--s2) in help-box and
  add-task-bar; -12px → calc(var(--s) * -1.5) in dialog-deadline.
- Radius: dialog-deadline press-enter-msg 8px → var(--radius-md).
- Font-family: drop two hardcoded 'Open Sans' declarations in
  magic-side-nav/nav-item (→ inherit) so the sidenav tracks the native
  --font-primary-stack.

Global typography is applied in src/styles/page.scss: body/app-root
get --font-primary-stack, --font-size-md (14px), line-height, letter-
spacing, -webkit-font-smoothing: antialiased. h1–h6 are wrapped in
:where() so their specificity is (0,0,0) — any component rule wins
trivially — and Material titles ([mat-dialog-title], .mat-mdc-dialog-
title, .mat-mdc-card-title) are excluded so Material's own type scale
is untouched. `.tabular-nums` is opt-in via the class only (the
earlier draft globally scoped `time`, which applied digit-width metrics
to non-numeric <time> content). code/kbd/pre/samp use --font-mono-stack.

Deliberately not set: text-rendering: optimizeLegibility and broad
font-feature-settings — both carry measurable paint cost on text-heavy
views. Browser defaults cover kern/liga.
2026-04-20 17:14:38 +02:00
Johannes Millan
a5e85decfd refactor(styles): add design-token scales (typography, radius, focus-ring)
Introduce coherent scales that replace ad-hoc hardcoded values and give
future components documented tokens to adopt.

Typography (src/styles/_css-variables.scss + src/styles/page.scss via
body styles in the sweep commit):
- --font-size-xs..3xl (11–28px) with --font-size-md as the body base.
- --font-weight-medium/semibold/bold (400 is the browser default, not
  declared).
- --line-height-tight/snug/normal (unitless so they scale with size).
- --letter-spacing-tighter/tight/normal/wide.
- --font-mono-stack for code/kbd/pre/samp.
- --font-primary-stack reordered to lead with native OS UI fonts
  (-apple-system, Segoe UI Variable Text, Roboto) with Inter/Open Sans
  as graceful fallbacks.

Radius scale:
- --radius-xs/sm/md/lg/xl and --radius-pill.
- --card-border-radius retargets to var(--radius-md) (4px → 8px) and
  --task-border-radius retargets to var(--radius-sm) (4px → 6px) for a
  softer surface treatment; both legacy aliases stay so 30+ consumers
  don't need touching.

Focus ring (keyboard accessibility):
- --focus-ring-width / -offset / -color tokens.
- .focus-ring opt-in utility class in src/styles/util.scss using
  outline on :focus-visible — Material components keep their own focus
  treatment.

Font stack dedup: src/styles/_fonts.scss is the single source of truth
for the stack. _css-variables.scss interpolates it into
--font-primary-stack; themes.scss passes the same list (as a literal
string, which is all mat.m2-define-typography-config accepts) — no more
"keep in sync" drift trap.

Also fix the inherited --transition-duration--leave (double dash) typo
on the light-theme body block so the override actually matches its
intended name.

docs/styling-guide.md documents all the new tokens, correct the
transition-duration values to match _css-variables.scss (-s 150ms,
-m 225ms), and adds a Focus Ring section with adoption guidance.
2026-04-20 17:13:11 +02:00
Johannes Millan
97caa1f30b style(tasks): tighten work-view task done-toggle and right margin
Shrink --done-toggle-size to 34px and reduce .all-controls-wrapper
right margin to var(--s-half) for a more balanced, asymmetric layout.
2026-04-20 15:48:40 +02:00
Johannes Millan
e9ee2c4e45 fix(task-view-customizer): scope Group By to current work context (#7279)
Group By: Tag / Group By: Project pulled tasks from every project when
used inside a specific project view, because de1b5d485c (fix for #7050)
deliberately switched the source to all undone tasks for these two
options. That closed #7050 but broke the more common case reported in
#7279 (and its duplicate #7290).

- TaskViewCustomizerService: revert customizeUndoneTasks() to the work
  context's undoneTasks$, drop _allUndoneTasksWithSubTasks$ and the
  cross-store selector/switchMap.
- Hide Group By: Project in a PROJECT context (it would collapse into
  one group) via a new availableGroupOptions computed signal backed by
  isActiveWorkContextProject$.
- Sanitize any stored group=project state inside the service's
  context-load subscription, not from a lazy panel effect — the panel
  is only instantiated when the mobile bottom-sheet customizer opens.
- Drop the now-redundant cross-context selection guard in
  WorkViewComponent and retire its #7269 test block: after this fix
  customizedUndoneTasks.list is always a subset of undoneTasks, so the
  scenario those tests constructed can no longer occur.
- Stub isActiveWorkContextProject$ in all WorkContextService test
  doubles so the service's toSignal() call has something to subscribe
  to.

Closes #7290.
2026-04-20 15:48:40 +02:00
Johannes Millan
ecba6c4240 docs(research): add §16 field data from #7270 follow-up
First post-release report on PR #7266 (v18.2.4): X11 widening fires
but doesn't rescue user on Ubuntu 24.04 + Intel Arrow Lake-P. Mesa
DRI still fails, GPU process segfaults in a loop. Lowers the 95%
estimate, promotes --disable-software-rasterizer to must-ship, and
upgrades core24/gpu-2404 migration urgency to 18.3.
2026-04-20 15:48:40 +02:00
Johannes Millan
2cefde61bf fix(electron): keep window reachable when show/hide shortcut has no tray
The globalShowHide shortcut called mainWin.hide() unconditionally. On
Windows/Linux hide() removes the taskbar entry, so with no tray icon
the window became unreachable.

- macOS: use hide() (dock icon always remains).
- Windows/Linux: hide to tray only when minimize-to-tray is on AND the
  tray was created; otherwise minimize() to keep a taskbar handle.
- Log whether Windows tray creation used the GUID path, to help diagnose
  cases where the icon lands in the hidden overflow area.

Refs #7282
2026-04-20 15:48:40 +02:00