- Wrap heatmap in collapsible "Activity" section, collapsed by default
- Lazy-load heatmap only when expanded to avoid loading full archive
- Disable autoFocus at all dialog call sites to prevent keyboard popup
- Icon-only action buttons on small screens with aria-labels
- Rename "Remove for" to "Skip for" with consistent confirmation dialog
- Save button raised, Remove button warn color
* feat(benchmark): add task title link rendering benchmark
- Creates benchmark page at /benchmark route
- Generates 1000 test tasks (1/3 no links, 1/3 URLs, 1/3 markdown)
- Measures initial render time
- Measures scrolling FPS during 2s scroll animation
- Includes toggle for link rendering (not yet functional)
- Baseline for measuring performance impact of link rendering
* feat(benchmark): increase to 5000 tasks and improve results display
- Increase task count from 1000 to 5000 for more measurable differences
- Add copyable results display with pre-formatted text
- Add 'Copy Results' button for easy sharing
- Include link rendering state in results output
- Format results in monospace font for better readability
* feat(benchmark): measure initial render from blank state
- Start with empty task list instead of pre-generating on page load
- Generate tasks only when benchmark runs
- Clear existing tasks before each benchmark run
- Use double requestAnimationFrame to ensure complete render
- Provides accurate measurement of initial render performance
- Reset now clears tasks instead of regenerating them
* refactor(benchmark): remove redundant reset button
Run Benchmark now handles resetting state, making the separate
reset button unnecessary
* feat(task-title): render URLs and markdown links as clickable links
- Detect URLs (https://, http://, file://, www.) and markdown [title](url) syntax
- Render as <a> tags with target="_blank" rel="noopener noreferrer"
- XSS protection: HTML-escape all user content, validate URL schemes
- ReDoS protection: limit URL length to 2000 chars in regex
- ~0 perf cost when disabled via renderLinks signal input
- string.includes() pre-check skips regex for plain-text tasks
- Direct HTML escaping (4 replace calls) instead of DomSanitizer for speed
- readonly input disables editing and uses inherit cursor
- Add unit tests: XSS protection, ReDoS, readonly mode, click propagation
* feat(config): add isEnableLinkRendering setting to short syntax config
Add a checkbox in Settings → Short Syntax to toggle clickable link rendering
in task titles. Defaults to enabled. Wired to the renderLinks input of task-title.
* feat(task-title): render links in planner-task and schedule-event via pipe
Add RenderLinksPipe to convert URLs and markdown links to clickable <a> tags
without including the full task-title component. Use [innerHTML] with the pipe
in planner-task and schedule-event to keep their existing styling intact.
* refactor(task-title): use RenderLinksPipe to eliminate duplicate link-rendering code
Remove inline URL/markdown processing (URL_REGEX, MARKDOWN_LINK_REGEX,
_escapeHtml, _isUrlSchemeSafe, displayHtml computed) from task-title
component and delegate to the shared RenderLinksPipe instead.
The hasUrlsOrMarkdown() fast pre-check (includes() scan) is preserved as
a template guard so the pipe is only invoked for tasks that actually
contain URLs or markdown - plain text tasks still use cheap interpolation.
* fix(task-title): Better XSS protection when rendering links
* fix(task-title): Move XSS tests
* fix(task-title): fix parentheses counting in markdown links
* fix(task-tests): add signal in one place to compute if links should be rendered or not
* fix(task-title): prevent clicks from propagating when they shouldn't
* fix(task-title): convert readonly inputs on task title
* fix(task-title): Add aria-label to the urls
* fix(task-title): mock pipe in tests
* fix: focus mode test
* fix(task-title): add ocmment why we have duplicated content
* fix(task-title): exclude a few more schemes
* fix(task-title): harden link rendering security and performance
- Fix ReDoS in MARKDOWN_LINK_REGEX by using [^()]+ instead of [^()]*
to break catastrophic backtracking (fixes CodeQL failure)
- Add single-quote escaping to _escapeHtml for defense-in-depth
- Replace permissive URL scheme fallback with strict /^[a-z]+:/
detection to reject tel:, mailto:, and other non-browsable schemes
- Remove /benchmark route from production (component kept for dev use)
- Extract LINK_HINT_* constants shared between pipe and component
- Add fast-path optimization to planner-task to skip [innerHTML]
for plain-text tasks
* perf(task-title): optimize link rendering hot path
- Replace 5 chained _escapeHtml regex calls with single-pass lookup table
- Replace new URL() in _ariaLabelForUrl with manual hostname extraction
- Hoist all regexes to module-level constants (SCHEME_RE, TRAILING_PUNCT_RE)
- Replace _stripUrlTrailing match() array allocations with charCode loop
- Replace _normalizeHref regex with startsWith checks
- Move helpers to module-level arrow consts (removes method dispatch overhead)
* refactor(task-title): extract hasLinkHints utility and add schedule fast-path
- Extract shared hasLinkHints() function from duplicated hint checks
- Add fast-path optimization to schedule-event (was missing, unlike
planner-task and task-title which already had it)
- Optimize _stripUrlTrailing to use single substring instead of
repeated slice allocations
- Simplify imports across consuming components
* fix(task-title): reject empty URLs and clean up benchmark component
- Reject empty markdown URLs [text]() that would produce broken
href="http://" links
- Remove unnecessary CommonModule import from benchmark component
- Remove redundant standalone: true (default since Angular 19)
* fix(task-title): make links clickable on touch devices and schedule view
Links were unreachable in two contexts due to inherited pointer-events: none:
- task.component sets pointer-events: none on task-title for touch-primary
devices (to allow drag-and-drop)
- schedule-event.component.scss sets pointer-events: none on all direct
children of :host (so clicks bubble to the host handler)
Fix by adding pointer-events: auto on <a> tags in both task-title and
schedule-event styles. This overrides the inherited value so links are
clickable while preserving drag behavior on surrounding text.
* fix(task-title): use unrolled loop regex to fix CodeQL ReDoS warning
The previous [^()]+ fix wasn't sufficient — consecutive non-paren
characters could still be split across iterations of the outer *
in exponentially many ways when the match fails.
Restructure to [^()]*(?:\([^()]*\)[^()]*)* where each iteration
of * MUST start with a literal '(' — eliminating partition ambiguity.
Verified: pathological 50-char input completes in <0.1ms.
* fix(schedule): lift links above ::before overlay with z-index
The schedule-event ::before pseudo-element at z-index: 3 covers the
entire host, intercepting pointer events before they reach <a> tags
even with pointer-events: auto. Add position: relative and z-index: 4
to links so they stack above the overlay.
* refactor(task-title): remove link rendering toggle, always render links
Remove the isEnableLinkRendering config option and all plumbing:
- Delete config model field, default, form checkbox, service signal
- Delete translation key and string
- Remove renderLinks parameter from pipe (always renders)
- Remove renderLinks input from task-title component
- Remove GlobalConfigService injection from 3 components that only
needed it for this setting
- Remove isLinkRenderingEnabled property from all 6 consuming components
- Remove mock setup from 3 test files
- Simplify hasLinkHints checks (no setting gate needed)
This eliminates ~100 lines of config threading code. The pipe's internal
fast-path still skips regex for plain-text tasks.
* chore: remove unused benchmark component
---------
Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
* fix: make sub-tasks visually distinct in search results
- Add parentTitle to SearchItem for sub-task breadcrumb display
- Show subdirectory_arrow_right icon for sub-tasks
- Display parent task title as breadcrumb (Parent › Sub-task)
- Add indentation, italic styling for sub-task titles
- Update unit tests for parentTitle resolution
Fixes#6785
* fix(search): move subtask styles inside ::ng-deep and simplify CSS
- Move .subtask-icon, .subtask-breadcrumb, .task-subtask inside the
::ng-deep block so styles reach projected content in mat-list-item
- Replace hardcoded › character with chevron_right Material icon
- Remove unused search-result-subtask class from mat-list-item
- Simplify CSS: drop extra font-size overrides, use consistent units
* refactor(search): use [class.*] bindings and fix orphan subtask styling
- Replace [ngClass] with [class.*] bindings to match project convention
- Remove unused NgClass import
- Align italic condition with breadcrumb condition (use parentTitle
instead of parentId) so orphan subtasks don't get unexplained italic
---------
Co-authored-by: Johannes Millan <johannes.millan@gmail.com>
- Replace drag handle with SVG done-toggle circle with checkmark draw-on animation
- Improve mobile UX: cdkDragStartDelay for touch, bottom-sheet context menu
- Move issue/repeat indicators from drag handle to tag-list as chips
- Add isFinishDayEnabled config toggle
- Reorder context menu: add sub task before duplicate, deadline as own entry
- Fix context menu not opening on swipe left
- Fix race condition in done toggle animation timeout
Override overflow:hidden/auto on nested containers (form-wrapper,
Material tab body) to prevent the browser's focus-scrolling algorithm
from resetting the config-page scroll position when form controls
receive focus.
Replace z-index: 333333 with var(--z-add-task-bar) and z-index: 44
with var(--z-right-panel). Standardize media queries to use @include
mq() mixins. Extend spacing variable replacements across the full
codebase for margin, padding, and gap properties.
Tasks arriving via sync/archive can have undefined tagIds, causing
a TypeError crash when mapping tasks to search items. Add optional
chaining to prevent the crash.
Closes#6864
Replace event_busy with flag/flag_circle icons for all deadline-related UI
elements (task badge, detail panel, context menu, dialog, planner, schedule
page, snack notifications) to avoid confusion with the "unschedule" action.
Convert sync providers from eagerly instantiated array to lazy-loaded
factories using dynamic imports. Make dataRepair and migrateLegacyBackup
dynamically imported at their call sites (error/migration paths only).
- getProviderById is now async with promise-cached instantiation
- _setActiveProvider guards against redundant re-activation
- Staleness counter prevents race conditions on rapid provider switches
Add getPluralKey() utility that uses the browser-native Intl.PluralRules
API to select the correct CLDR plural category (one/few/many/other) for
any locale. This enables languages like Romanian, Russian, Polish, and
Arabic to provide proper plural forms beyond simple singular/plural.
Key changes:
- New getPluralKey() utility with locale-aware fallback via TranslateStore
- Rename translation keys: SINGULAR→ONE, PLURAL→OTHER (CLDR convention)
- Nest banner TXT_MULTIPLE keys into ONE/OTHER for proper English grammar
- Migrate all 27 locale files to new key structure
- Add 10 unit tests covering CLDR categories, fallback, and edge cases
Closes discussion #6698
- fix(issue): use issueProviderKey instead of hardcoded OPEN_PROJECT_TYPE
in handleIssueProviderHttpError$ so error messages show the correct provider
- Replace duplicate _getDateStr with getDbDateStr utility (2 files)
- Replace duplicate _formatTime/formatMs with msToString utility (3 files)
- Fix direct uuidv7 import to use wrapper in clean-slate.service.ts
- Replace manual text truncation with @include truncateText() (10 files)
- Replace manual centering with @include center (10 files)
- Replace manual border with @include extraBorder() (19 files)
* feat: add label to heatmap for repeat task
* style(e2e): fix prettier formatting in planner spec
* fix(focus-mode): clear stale _isResumingBreak flag when isPauseTrackingDuringBreak is enabled (#6534)
When both isSyncSessionWithTracking and isPauseTrackingDuringBreak are
enabled, pausing and resuming a break left _isResumingBreak stale,
causing the next manual tracking start to dispatch clearResumingBreakFlag
instead of skipBreak. Refactor syncSessionResumeToTracking$ to explicitly
dispatch clearResumingBreakFlag in this case.
* refactor(sync): unify JWT expiry to 365 days for all auth methods
Replace separate JWT_EXPIRY_MAGIC_LINK (365d) and JWT_EXPIRY_PASSKEY (7d)
constants with a single JWT_EXPIRY (365d). The auth method only matters
during login — once a JWT is issued, it represents a verified session
regardless of how the user authenticated.
* feat(start-of-next-day): respect startOfNextDayDiff offset in today view
Thread startOfNextDayDiffMs through AppState, selectors, meta-reducers,
and task component so that "today" membership correctly accounts for the
user's configured day-start offset. When startOfNextDay=4 (4 AM), at
2:30 AM the app now correctly treats the previous calendar day as "today".
- Add isTodayWithOffset utility for offset-aware date comparison
- Store startOfNextDayDiffMs in AppState alongside todayStr
- Update all selectors (work-context, planner, task, overdue) to use offset
- Update meta-reducers with defensive fallbacks for state access
- Fix task component computed signals (isOverdue, isScheduledToday, etc.)
- Add 48 new tests covering offset boundary conditions
* style(planner): remove unused eslint-disable directive
* fix(start-of-next-day): use offset-aware date in ensureTasksDueTodayInTodayTag effect
Replace raw getDbDateStr() and getDateRangeForDay(Date.now()) with
store-derived todayStr and offset-adjusted range in task-due.effects.ts.
Without this, the effect would use the wrong day between midnight and
the configured startOfNextDay hour.
Also add selectOverdueTasks offset boundary tests.
* refactor(start-of-next-day): use DateService for offset-aware today checks
Add DateService.isToday() method that encapsulates the startOfNextDayDiff
offset logic, replacing scattered isToday()/getDbDateStr() calls across
effects, services, and components.
- Add isToday(date) to DateService for DRY offset-aware checks
- Fix task-repeat-cfg.effects.ts: 6 isToday/getDbDateStr calls
- Fix task-repeat-cfg.service.ts: isToday call
- Fix task-context-menu-inner.component.ts: 6 isToday/getDbDateStr calls
- Fix task-related-model.effects.ts: getDbDateStr call
- Fix work-context.service.ts: getDbDateStr call
- Remove duplicate planTaskForDay handler from tag.reducer.ts
(already handled by planner-shared meta-reducer with offset)
* refactor(start-of-next-day): migrate remaining isToday() calls to offset-aware DateService
Replace 6 call sites still using the non-offset isToday()/isYesterday()
with DateService methods that respect startOfNextDayDiff. Also adds
isYesterday() to DateService and uses isTodayWithOffset in legacy backup
migration. Behavior is identical at offset=0 (default).
* fix(start-of-next-day): fix offset bugs, sync regression, and code quality issues
- Fix wrong config path in legacy backup migration (misc.startOfNextDay)
- Restore sync readiness check (filter+first instead of take(1))
- Restore SYNC_AFTER_ENABLE in setInitialSyncDone conditions
- Use offset-aware dates in addAllDueToday/addAllDueTomorrow
- Make isSameDay offset-aware and pass offset through planner selectors
- Fix TagSettingsPageComponent selector from 'project-settings' to 'tag-settings'
- Hide settings link for virtual TODAY tag
- Revert direct ru.json edits (only en.json should be edited)
- Add standalone:true and use takeUntilDestroyed in settings components
- Restore Math.max(duration,1) for zero-duration overlap detection
- Remove dead code, stale CSS, and commented-out HTML
- Add input validation clamping in DateService.setStartOfNextDayDiff
* fix(start-of-next-day): fix offset bugs in overdue detection, planner display, and LWW sync
- Fix isOverdue ignoring offset for dueWithTime tasks in task.component
- Remove duplicate moveBeforeTask handler from tag.reducer (handled by meta-reducer)
- Add skip(1) and hydration guard to setTodayStr$ effect to prevent race condition
- Move side effects from map() to tap() in global-config.effects
- Fix isSameDay double-offset bug in planner.selectors for scheduled tasks/events
- Replace unsafe `as any` casts with proper PlannerState types
- Use safe optional chaining for todayStr access in meta-reducers
- Refactor handlePlanTaskForDay to use helper functions with hasChanges optimization
- Extend syncTodayTagTaskIds in LWW meta-reducer to handle dueWithTime changes
- Fix absolute import path in global-config.effects
- Add @deprecated to isToday() in favor of offset-aware alternatives
* fix(config): migrate task dueDays when startOfNextDay offset changes
When the "start of next day" offset changes and causes todayStr to shift,
existing tasks with dueDay matching the old todayStr are now migrated to
the new todayStr so they remain classified as "today" tasks.
* refactor(config): use switchMap and document archive task exclusion
Replace mergeMap with switchMap in setStartOfNextDayDiffOnChange
effect to better communicate intent (only latest emission matters).
Add comment clarifying archived tasks are intentionally excluded
from dueDay migration.
* test(sync): add LWW tests for dueWithTime → TODAY_TAG sync
Cover the dueWithTime path in syncTodayTagTaskIds that was added
but had no test coverage. Tests verify TODAY_TAG membership updates
when dueWithTime changes via LWW sync.
* fix(side-nav): make settings button visible on Android mobile (#6561)
Change nav-list height to min-height so overflow enables scrolling,
and add safe-area padding for Android gesture navigation bar.
* build: add worktrees to gitignore
* fix(tasks): guard against undefined tagIds/subTaskIds on corrupted task data
Add _ensureTaskArrayProperties() as early data repair step to initialize
undefined tagIds, subTaskIds, and attachments to []. Add defensive optional
chaining in daily-summary and board-panel components to prevent TypeErrors
when tasks from legacy/corrupted data have missing array properties.
Closes#6580
* refactor(date-formatting): improve date formatting
* fix(tests): change tests mock object for DateTimeFormatService
* refactor(date-adapter): implement custom date adapter for locale-aware formatting and parsing
* refactor(date-adapter): enhance date parsing and formatting logic for locale support
* refactor(test): remove redundant date format tests
* refactor(date-time-format): improve getting locale-specific format
When the "finish day before exit" dialog appeared and the user clicked
Cancel, setDone() was called unconditionally, signaling Electron that
the before-close handler completed and causing the app to quit anyway.
Adding an early return skips setDone(), keeping the handler pending so
the app stays open. Fixes#6563.
Remove duplicate LegacySyncProvider enum and use SyncProviderId everywhere.
The two enums had identical values but were separate types for historical
reasons, making the "Legacy" name misleading since providers are actively used.
Add delete button to habit settings dialog with confirmation. Add
collapsible disabled habits section to the habits page with enable,
edit, and delete actions. Remove simple counter section from global
config page and delete the unused SimpleCounterCfgComponent.
* fix(sync): add button to authenticate with dropbox to sync settings
* fix(sync): add text to clarify if Dropbox is currently authenticated or not
* fix(sync): remove try-catch block that can prevent errors with the storage from bubbling up. It will now show a snackbar with a generic incomplete_cfg message
* fix(sync): allow forcing re-authentication of a sync provider (only used for Dropbox now)
Replace O(n) array lookups with Map-based O(1) lookups for parent tasks,
projects, and tags. Pre-compute lowercase search text once at mapping
time instead of on every keystroke. Memoize archive task mapping since
archive data never changes. Fix crash when subtask parent is missing
from the task array by using null-safe Map lookup with fallback.
- Change server-side pruning log from info to debug
- Increase vector clock sanitize limit from 3x to 5x MAX
- Add Formly hierarchy comment for WebDAV settings
- Add test documenting isLikelyPruningArtifact false positive limitation
- Replace `as any` with proper Partial<TaskRepeatCfgCopy> typing
- Add comment explaining `order` in SCHEDULE_AFFECTING_FIELDS
- Cache Intl.Collator instance, recreate only on locale change
- Document plugin management 30s timeout rationale
- Extract _isLikelyPruningArtifact as standalone pure function
- sync.effects.ts: Replace arbitrary delay(100) with event-loop yield
(setTimeout 0) so NgRx persistence effects actually complete before
sync runs, instead of hoping 100 ms is "enough"
- daily-summary.component.ts: Replace arbitrary delay(70) with
event-loop yield for the same reason — let the archive persistence
write finish before re-reading, instead of a magic timeout
- calendar-integration.service.ts: Log localStorage JSON parse errors
instead of silently swallowing them in an empty catch block
- app.guard.ts: Log the actual error in ValidTagIdGuard and
ValidProjectIdGuard before returning false, so failed lookups are
diagnosable
- add-task-bar-issue-search.service.ts: Log issue-provider search
errors instead of silently returning an empty array
- task-detail-panel.component.ts: Log failed issue-data fetches
instead of silently returning null
- linear-common-interfaces.service.ts: Log Linear API connection-test
failures instead of silently returning false
- insert-blocked-blocks-view-entries-for-schedule.ts: Replace vague
"for some reason" comments with actual root-cause explanation —
splice() operations scatter split-continued entries non-contiguously,
requiring time-based (not index-based) movement
https://claude.ai/code/session_01RHxKdsHHtFiXs3qbtbPBQC
Co-authored-by: Claude <noreply@anthropic.com>
- fix(tasks): preserve selectAllTasks memoization by only filtering when
undefined entities exist, avoiding new array allocation on every call
- fix(sync): replace mutable ARGON2_PARAMS export with getter/setter to
prevent test pollution across spec files
- fix(sync): convert tag-task-page from async pipe to toSignal pattern
to fix OnPush change detection after bulk sync state updates
- fix(sync): remove E2E navigation workarounds that masked the rendering
bug now fixed by the toSignal conversion
- fix(sync): add clarifying comments for archive-wins sibling conflict
resolution and waitForSyncWindow switchMap concurrency semantics
- fix(ios): add hex preview of first 16 bytes to WebDAV UTF-8 decode
error for easier debugging
- docs: add JSDoc to getDiffInWeeks noting negative diff behavior
https://claude.ai/code/session_01Y51QDFEdvrJ9VVgp9XfWLJ
Co-authored-by: Claude <noreply@anthropic.com>
* feat: introduce habit tracking page and enhance simple counters with streak tracking and comprehensive configuration options.
* feat: add habit tracker for simple counters with navigation and internationalization.
* feat: Introduce habit tracker component and tests for simple counter streak duration calculation.
* Fix the dialog style that appears in the Habit interface
* bug fix
* Fixed translation and time zone issues
* habit icon
* app features
* Add a Settings button to the Habits page; clicking it opens the Settings interface and automatically expands the corresponding configuration section.
Clicking a habit icon allows you to configure that habit. To add a new habit, click the “Add” button.
* If a habit does not have a custom icon, its first character can be displayed inside a circular placeholder instead.
* The dialog box appears only after you release the button following a long press.
* Add the functionality to display names; clicking the button shows the habitual name. This can be persisted
* bug修复:Date adjustment (left/right) in shrink mode is not functioning as expected.
* Set Text Icon
* Fix abnormal icon size
* These development artifacts should not be committed to the repository.
* Lost Chinese translations
* Flaky Date-Dependent Tests
* Logic error in streak calculation
* Fix the icon issue
* Translation file violation
Use of any type
---------
Co-authored-by: Xinjie <xinjie_zhou@163.com>
The button was duplicated in both config-page.component.ts and
sync-form.const.ts. Keep the one in sync-form.const.ts as it's
properly placed in the Advanced Settings section.
- Implement full test suite for ScheduleComponent covering:
- Navigation state management (_selectedDate signal)
- Week/month navigation (goToNextPeriod, goToPreviousPeriod, goToToday)
- Today detection (isViewingToday computed)
- Context-aware time calculations (_contextNow)
- Schedule days computation with contextNow/realNow
- Current time row display logic
- Days to show calculations for week and month views
- Add ScheduleService tests for:
- getDaysToShow with reference dates
- getMonthDaysToShow with padding days
- buildScheduleDays parameter handling
- getDayClass with reference month support
- Fix test setup:
- Use TranslateModule.forRoot() for proper i18n support
- Add complete mock methods for ScheduleService
- Handle timing-sensitive tests with appropriate tolerances
- Use correct FH constant value (12) for time row calculations
- Remove unused CollapsibleComponent import from config-page
All 32 ScheduleComponent tests passing