Skip to content

fix(caldav): keep two-way sync working after the first sync (#1822) - #1835

Merged
chrisvel merged 1 commit into
mainfrom
fix/issue-1822-caldav-sync
Oct 8, 2026
Merged

chrisvel merged 1 commit into
mainfrom
fix/issue-1822-caldav-sync

Conversation

@chrisvel

@chrisvel chrisvel commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Description

Outbound sync glued the server's href onto server_url, so a server under a path prefix got a doubled path on every push after the first, and a push rejected with 412 left the task in conflict forever (the local winner of a conflict was also never pushed). This fixes both, settles conflicts on the next pull using LAST-MODIFIED, syncs dates in the user's timezone, and on the server side serves getctag in the CalendarServer namespace, bumps updated_at on tag changes, keeps Tududi-only statuses on client edits and answers no-uid-conflict instead of creating a duplicate row.

Type of Change

  • Bug fix (fixes an issue)
  • New feature (adds functionality)
  • Breaking change (breaks existing functionality)
  • Documentation/Translation update

Related Issues

Refs #1822

Testing

How did you test this?

New caldav-two-way-sync.test.js (8 of its 13 tests fail on main), full backend suite and npm run lint (0 errors).

  • npm run pre-push passes

Checklist

  • This is not an AI slop crap, I know what I am doing
  • Self-reviewed my own code, follows project style guidelines
  • Tests, docs, migrations, and translations updated (if applicable)

Additional Notes

Out of scope: reminders with times, webhooks, projects as separate lists, and detecting remote deletions without a sync-token; the "ghost task" state could not be reproduced on current main.

- Resolve remote hrefs against the server origin so a server under a path
  prefix no longer gets a doubled path on every push after the first.
- Settle push conflicts (412) on the next pull instead of skipping the task
  forever, push the local version when it wins, and compare LAST-MODIFIED
  for last-write-wins.
- Pull and push dates in the user's timezone, and push tags/project/parent.
- Serve getctag in the CalendarServer namespace and touch updated_at when a
  task's tags change, so clients see Tududi-side edits.
- CalDAV PUT keeps Tududi-only statuses and order, and answers
  no-uid-conflict instead of creating a duplicate row.
@chrisvel
chrisvel merged commit 956c45b into main Oct 8, 2026
5 checks passed
danathar-atomic-hive Bot added a commit to Danathar/tududi that referenced this pull request Oct 11, 2026
* fix(calendar-feeds): fetch iCal feeds over keep-alive agents (chrisvel#1790) (chrisvel#1806)

The feed fetcher built its own http/https agents without keepAlive, unlike
Node's global agent. On some Docker hosts that stalls a Google Calendar
download until the 10 s timeout. Enable keepAlive on both agents; the
connect-time address guard still runs for every new socket.

* fix(habits): count habits checked in today in the Done for now card (chrisvel#1803) (chrisvel#1807)

The check-in button treats a habit counted in distinct days (weekly 3x) as
done once today has a check-in, but the overview counter only counted
habits whose whole period goal was met, so it stayed at 0/5 while the
cards showed done. Both now use one isHabitDoneForNow helper.

* fix(views): stop the view banner clipping the Sort by menu (chrisvel#1796) (chrisvel#1808)

The banner wrapper had overflow-hidden, which cut the absolutely
positioned sort/group menu off at the banner's bottom edge. The inner
panel already rounds its own corners, so the clip is not needed.

* fix(today): save a status picked from a task row on /today (chrisvel#1805) (chrisvel#1809)

The status menu hands the changed task to onTaskUpdate and relies on the
page to save it. The Today page's handlers only update local state, since
its rows save their other edits themselves, so a new status showed in
the UI and was gone after a reload. TaskRow gains saveStatusChanges: it
PATCHes the status and passes the server copy on, routing a Done pick
through the completion handler. Today's agenda and Not planned rows
opt in.

* fix(caldav): send the stored etag as a quoted entity-tag in If-Match (chrisvel#1779)

Remote task pushes sent the stored etag verbatim. It is stored without quotes, but the quotes are part of an entity-tag (RFC 9110 8.8.3), so Radicale rejected every push with 412 and the task was marked as a conflict and never retried. Quote the value when building the header, and leave a weak tag as it is. Fixes chrisvel#1778.

* feat(i18n): add Slovak translation (chrisvel#1741)

Adds Slovak (sk) as the 26th language: the full app catalog with Slovak
plural forms, quotes, and the landing page. Registers sk in the i18n
supported languages, the language picker, the landing locale list and the
backend Upcoming day grouping, and adds a "Slovak" label to every locale.

* fix(mcp): atomically create projects with tags (chrisvel#1529)

* fix(mcp): atomically create projects with tags

* fix(mcp): extend atomic tag creation to create_task and update_task

Issue chrisvel#1526 was closed by PR chrisvel#1531, which fixed the SQLITE_BUSY symptom
by making tag findOrCreate calls sequential across all three MCP tools,
but without atomicity: a failure partway through tag creation still
leaves the parent record committed with missing or partial tags.

This PR already wrapped create_project in a transaction to close that
gap. Extend the same pattern to create_task and update_task, wrapping
task creation or update, tag creation, and tag association in one
sequelize.transaction so a failure at any point rolls back cleanly.

taskRepository.create() now accepts an options object so the task
creation can be run inside that transaction.

Add tests mirroring the existing create_project rollback coverage for
both handlers, plus coverage for update_task's tag replace/clear
semantics.

---------

Co-authored-by: Chris Veleris <chrisveleris@gmail.com>

* feat(admin): show items each user created on the users table (chrisvel#1812)

Adds an Items column with the total a user created (tasks, habits,
projects, notes, areas, goals, tags, people, views, inbox) and when
they last created one; clicking it opens the per-kind breakdown.
App-made rows (recurring instances, system tags, self-people) are
left out.

* fix(i18n): add the seats line and missing trial and consent strings to the Slovak landing page (chrisvel#1816)

The Slovak catalog from chrisvel#1741 had 5 Cloud plan features where the other
languages have 6, which failed the landing catalog test on every PR, and
it lacked the trial, cookie consent and blog strings added since.

* release: v1.7.12

* fix(release): generate release notes for every release (chrisvel#1817) (chrisvel#1820)

Stable releases took their body from CHANGELOG.md, and an empty section
still passed the size check, so they shipped with only the docker pull
line. create-version.sh also missed the heading right below Unreleased,
stacking an empty version heading on every release.

scripts/release-notes.sh now builds every release body: the changelog
section when it has text, then GitHub's generated notes since the
previous tag. With --apply it rewrites an existing release, for the
backfill.

* feat(admin): last 7 days trends on the admin dashboard (chrisvel#1824)

Signups per day against the week before, activation of new users,
weekly active users and waitlist joins. On a hosted instance it also
shows trials started, trials ending soon, cancellations and failed
payments.

* fix(areas): return area id from create and update (chrisvel#1829)

The frontend adds these responses straight to the areas store, and the
area picker selects by id, so a new or edited area could not be assigned
to an existing project until a reload.

Fixes chrisvel#1828

* fix(mcp): let create_task and update_task assign an area (chrisvel#1814) (chrisvel#1831)

Accept area_id and area_uid on create_task and update_task, validated with
the same area access check the web API uses, and include the Area in task
tool responses so directly assigned areas are no longer null.

* fix(tasks): show blocked state on both related tasks and keep it after reload (chrisvel#1810) (chrisvel#1833)

The project page returned raw tasks without is_blocked, so the Blocked
badge vanished on reload. A relation change also only refreshed the task
being edited, so the linked task's row never picked up its new state.

* fix(habits): keep checked-in habits in Today, Plan, Upcoming and All tasks (chrisvel#1821) (chrisvel#1834)

A check-in marked the habit task as done, so every task list dropped it
for good and only habits never checked in were left. Habits now stay
open; suggestions and Upcoming show a habit on the days it still asks
for a check-in, and a migration reopens habits already marked done.

* fix(caldav): keep two-way sync working after the first sync (chrisvel#1822) (chrisvel#1835)

- Resolve remote hrefs against the server origin so a server under a path
  prefix no longer gets a doubled path on every push after the first.
- Settle push conflicts (412) on the next pull instead of skipping the task
  forever, push the local version when it wins, and compare LAST-MODIFIED
  for last-write-wins.
- Pull and push dates in the user's timezone, and push tags/project/parent.
- Serve getctag in the CalendarServer namespace and touch updated_at when a
  task's tags change, so clients see Tududi-side edits.
- CalDAV PUT keeps Tududi-only statuses and order, and answers
  no-uid-conflict instead of creating a duplicate row.

* fix(mcp): let create_task and update_task assign a person (chrisvel#1836) (chrisvel#1837)

The MCP task tools had no assigned_to field, so assigning a task needed a
direct API call. Both tools now accept a person UID (null unassigns),
checked with the same validateAssignee rule as the REST routes, and notify
the new assignee the same way.

* fix(mcp): support is_maintenance in project tools (chrisvel#1815) (chrisvel#1830)

create_project and update_project now accept and persist is_maintenance,
keeping it exclusive with a linked goal like the web goal picker, and
list_projects, get_project, create_project and update_project return it.

* style: projects and inbox cosmetic tweaks (chrisvel#1832)

* style(projects): compact shared card for the projects grid

Replace the image-banner project card with a shorter EntityCard: two-line
title, subtle description, people and area pill above a bottom-pinned
progress row with due date and status, so rows line up. Avatars use flat
per-person tints instead of a gradient, From Template is a toolbar button,
and the grid tops out at three columns.

* style(projects): table layout for the projects list view

The list view is now a table of project rows: name with area, status badge
with the status picker, summary, members, due date, progress and an actions
menu. Name and Due headers sort the list, columns collapse on smaller
screens, and rows stay draggable for custom order. From Template gets a
solid secondary button style that reads in light and dark mode.

* style(project): calmer project page with header card and overview column

Replace the full-width banner with a tinted header card (image tile,
status, due and start dates, description, pin/share/edit, progress against
time used). The Tasks tab becomes Overview: four count cards and the task
list on the left, and People (linked to profiles), Project info and recent
notes on the right. Notes open in a read-only preview with an Edit button.
The metrics panel and its toggle are removed.

* style(inbox): detach the open recently captured item into its own card

Clicking an inbox item splits the list around it and lifts the item into
a separate card with a shadow, animating the gaps and corners so the open
item no longer blends into the list.

* style(upcoming): show full task titles on the upcoming cards

Titles wrap up to three lines instead of truncating, and the status
controls move to a row under the details so the title gets the full
card width.

* style: breadcrumbs, tighter area modal, no project stats strip

Detail pages get a breadcrumb bar under the navbar (area, project, goal,
task, tag, note, habit, person), replacing the project hero's own trail.
The area modal fits its content and is wide enough for one row of colors.
The project page drops the stats strip and its tab reads Tasks.

* style(notes): full-bleed note pane, left-aligned breadcrumbs

Notes fill the content area edge to edge without the card bezel, and the
breadcrumb bar uses the detail pages' full width and padding.

* feat(layout): full-height sidebar, no top navbar

The sidebar now runs top to bottom in one color, with the logo,
notifications, account menu, search and quick capture at its top. The page
takes the full height beside it. When the sidebar is closed a small button
brings it back, and modals, toasts and the capture box no longer leave room
for a navbar.

* fix(layout): sidebar menus open above the sidebar column

* style(layout): sidebar on the app canvas, page as a rounded panel

Replaces the sidebar-only header. The sidebar runs full height on the canvas
color with the logo and a collapse button at its top. The page is a rounded
panel beside it with its own top bar (search, capture, timer, notifications,
account). The bar ends where the old navbar did, so modals, toasts and
search keep their positions.

* style(layout): collapsed sidebar rail, photo under the top bar, pomodoro off by default

The collapsed sidebar keeps an icon rail with the logo, main links and
section links on tablet and desktop. The page background photo runs under
a tinted, blurred top bar, and the search bar fills the bar. New accounts
start with Pomodoro off. The project tab row drops its full-width rule and
the active tab underline sits under the label.

* style(tasks): quiet sheet task lists

Task lists render as one shared surface with soft dividers instead of
separate cards. Inside a sheet, priority shows as a dot before the title,
tags as colored dots, and the status button as a borderless pill. Lists
without the sheet class (Kanban, Eisenhower, Upcoming columns, Today)
keep the card look.

* style(tasks): one task row look everywhere

Every task list uses the quiet sheet: no dividers, an open task splits
away from the rows around it, priority shows as a soft badge (with the
priority dropdown's icon) under the title plus title weight, and the
subtask progress bar is gone. Upcoming, Kanban, Eisenhower, Today, the
subtasks card, a person's assigned tasks and the Everyone board all render
these rows. The expanded row's priority button uses the same badge. Also
rounds the collapsed Inbox header and makes the Everyone stats readable.

* release: v1.8.0

* fix(plan): keep the planning tip bar under the navbar menu

The planner's sticky tips bar carries z-40 so it clears the timeline's
own layers, but the plan column had no stacking context of its own, so
the bar also rose above the navbar (z-30) and covered the avatar menu.
The column is now isolated. Also a singular form for the missed-blocks
tip ("1 planned block already passed").

* feat: first-plan welcome screen for new accounts (chrisvel#1838)

* feat: first-plan welcome screen for new accounts

A new account's first visit to Today opens /welcome: one box, one line
per thing, then Plan my day turns the lines into tasks and a started
plan for today. Lines dated on another day become tasks but stay off
the plan. Skip records the screen as seen.

- users.onboarded_at, backfilled for existing accounts; demo user set
- POST /api/onboarding/complete; onboarded_at on current_user/profile
- Areas and Habits blank slates get one-click starters (BlankSlate presets)
- Admin trends: planned a first day (3+ tasks) and kept planning (3 days
  in week one)
- docs/24-onboarding.md

* feat: show the welcome screen to every account once, reopen it as Brain dump

No backfill of onboarded_at, so existing accounts get the screen on
their next visit to Today (the demo account is set when prepared).
/welcome stays reachable from the navbar menu as Brain dump; lines are
appended to today's plan with carry-over instead of replacing it.

* feat: welcome screen as a modal over the app

The welcome is now a modal with a blurred backdrop over Today instead of
a full-screen route, so the app stays visible behind it. Closing it,
Escape or a click outside count as skipping on the first visit. Brain
dump in the navbar menu opens the same modal on any page; after planning
it fires dailyPlanChanged so Today reloads in place.

* feat: brain dump opens the planner

Plan my day now adds the lines to today's plan and opens /today/plan,
where the day gets its times and start. Launched from the planner, the
modal hands the tasks over the way the Add box does, so the planner's
own save never drops them. Today no longer needs a reload hook.

* fix: keep the status menu on screen on phones

On the task page the status control sits at the left edge of a narrow
screen, and its menu hung off the control's right edge, so most of it
ran off the left side of the viewport. The menu now picks its
horizontal alignment when it opens, the way it already picks up or down.

* feat: keep bringing more candidates to the daily plan

Show more on the planner's left column used to stop at the Planning
setting (20 suggestions by default). It now pages through everything:
once the loaded suggestions run out it asks the server for the next
twenty, until every task that qualifies has been shown.

GET /api/daily-plan/candidates takes ?suggested_limit=N (1 to 1000) and
returns suggested_total so the client knows when it is done.

* feat: starter picker welcome screen with the welcome video

Every account sees a full-screen starter picker once on its first visit to
Today, in place of the brain dump modal opening on its own. Four starters
(household, work and a side project, studying, just me) seed areas, goals,
projects, habits, example tasks and a note in one transaction; Start empty
records the choice and opens the brain dump. The welcome video plays beside
the cards. The choice is saved in users.onboarding_starter, seeded tasks
carry example_of until first edit, and Today offers Remove examples and a
brain dump card during the first week.

* fix: show the starter picker on any signed-in page

* feat: starter picker as a /welcome page inside the layout

* style: wider welcome video with the starters in a side column

* style: welcome video first, starters beneath it

* feat: welcome page as a two-step wizard

* feat: welcome page is the video and one button

* refactor: remove the brain dump modal

The welcome page replaced it: the modal no longer opened on its own, and
the navbar item, its open-state store, the complete endpoint, its strings
and docs go with it.

* fix(project): show planned tasks under the Open filter on the project page (chrisvel#1846)

The project page listed only not started, in progress and waiting tasks
under Open, so planned tasks were visible only under All together with
every completed task. Open now uses the shared isTaskActive rule, the
same one the Tasks page uses, and planned tasks sort after not started
ones.

Fixes chrisvel#1842

* docs(readme): point the Swagger docs link at /api-docs (chrisvel#1847)

The feature list said the Swagger UI lives at /api/v1, but Express
mounts it at /api-docs.

Refs chrisvel#1843

* fix(caldav): detect tasks deleted on the remote server (chrisvel#1848)

The pull phase only learned about remote deletions through a
sync-collection report, which needs a sync-token the server never
returns for the calendar-query used on every pull. A task deleted in
another client therefore stayed in Tududi for good. A full listing is
now diffed against the sync states, and anything with a recorded href
that the server no longer lists is treated as deleted.

A task edited in Tududi after its remote copy was deleted used to be
marked as a conflict that nothing could ever settle, so it was never
pushed again. Unless the calendar asks for manual resolution, the local
edit now wins and the next push creates the resource again.

Task rows also encode the uid in their link, since a uid chosen by a
CalDAV client can hold characters the router cannot take as is.

Refs chrisvel#1822

* feat(project): edit in place, note and attachment widgets, softer task rows (chrisvel#1849)

* style(project): notes and attachments as widgets in the overview column

The project page drops its Notes and Attachments tabs. Notes open in a
side panel from an overview widget, attachments live in their own widget
with optional titles and an inline viewer, and the stats strip is gone.
The attachment viewer and side panel are shared with the Notes page,
which loses its preview modal.

* style(tasks): soft dividers between sheet rows, edge-to-edge hover

Task sheets get a one-pixel line between rows again. It drops away on
either side of an open task, which already sits apart. Hover tints the
whole row band instead of a rounded pill inside it.

* feat(project): edit the project in place on its page

The project page no longer opens the edit modal. The name and description
edit on click (Enter or leaving saves, Esc restores), the status chip is
a menu, and every Project info row (area, goal, priority, due date, tags,
color) opens its picker under the row and saves on choice. Each field
sends its own PATCH, so nothing else on the project is touched.

* style(project): pencil before the info value so it stays right-aligned

* Shared side panel for entity create and edit (chrisvel#1850)

* style(project): notes and attachments as widgets in the overview column

The project page drops its Notes and Attachments tabs. Notes open in a
side panel from an overview widget, attachments live in their own widget
with optional titles and an inline viewer, and the stats strip is gone.
The attachment viewer and side panel are shared with the Notes page,
which loses its preview modal.

* style(tasks): soft dividers between sheet rows, edge-to-edge hover

Task sheets get a one-pixel line between rows again. It drops away on
either side of an open task, which already sits apart. Hover tints the
whole row band instead of a rounded pill inside it.

* feat(project): edit the project in place on its page

The project page no longer opens the edit modal. The name and description
edit on click (Enter or leaving saves, Esc restores), the status chip is
a menu, and every Project info row (area, goal, priority, due date, tags,
color) opens its picker under the row and saves on choice. Each field
sends its own PATCH, so nothing else on the project is touched.

* style(project): pencil before the info value so it stays right-aligned

* feat(side-panel): shared entity side panel for area and tag forms

Adds EntitySidePanel with a slide-in, scrim, Escape handling, a discard
guard and a footer with delete, cancel and submit. AreaModal and TagModal
now render through it. Project, task, goal and the other entities still
use their existing surfaces.

* feat(side-panel): project form uses the shared side panel

ProjectModal now renders in EntitySidePanel with every field visible,
instead of collapsible sections behind icon toggles. Goal changes count
as unsaved changes. The panel stays open while the delete confirmation
is showing.

* feat(side-panel): goal create and edit use the shared side panel

GoalModal replaces the inline goal form on the goal page and the
/goal/new route. The sidebar, the Goals page and the goal page open the
panel. A new goal still opens its page once saved. /goal/new now
redirects to /goals.

* feat(side-panel): person form uses the shared side panel

PersonModal renders in EntitySidePanel. The relationship picker is now a
select, so it no longer needs its own portal menu.

* feat(side-panel): member add and edit use the shared side panel

MemberModal and MemberEditModal render in EntitySidePanel. The panel now
falls back to English labels for Cancel when a translation is missing.

* feat(side-panel): habit create uses the shared side panel

HabitModal creates a habit in EntitySidePanel and reuses HabitSettings.
Habit edits stay inline on the habit page, which now only handles existing
habits. /habit/new redirects to /habits. Settings show the saved habit
directly instead of an optimistic draft.

* feat(side-panel): task core fields edit in the shared side panel

TaskModal edits name, description, status, priority, project, area, due
date and tags, and sends only the fields that changed. The task page header
gets an Edit details item. Recurrence, relations, subtasks and reminders
stay on the task page. Task creation stays in the capture box.

* style(inbox): soft dividers between inbox rows

Inbox rows get the same one-pixel line as task sheets. It drops away next
to the open item, which already sits apart from its neighbours.

* fix(inbox): keep row corners steady when an item opens

The divider border grew and shrank with the open item, which animated the
rounded corners of the rows beside it. The border now always takes up its
1px and only its colour changes.

* fix(inbox): stop rounded corners animating when an item opens

The row wrapper transitioned every property, so border radius animated
from square and flashed when an item opened. Only margin and padding
animate now; the corners switch at once.

* fix(inbox): open the item and its list state in one render

The list only learned an item was open from an effect after the card
rendered, so the card sat in a square row for a frame before its rounded
corners took over. The click now updates both in the same render.

* style(inbox): animate corner rounding with the open item

The row wrapper transitions every property again, so rounded corners ease
in and out with the spacing now that the open state changes in one render.

* fix(inbox): draw the open card on the row wrapper so corners animate together

The open card kept its own rounded corners, while the rows beside it changed
theirs over 300ms, so for a moment the corners did not match. The wrapper now
carries the rounding, background and shadow, so the open item and its
neighbours animate on the same element. The card inside is flat.

* feat(side-panel): shared entity side panel for create and edit forms (chrisvel#1851)

* feat(side-panel): shared entity side panel for area and tag forms

Adds EntitySidePanel with a slide-in, scrim, Escape handling, a discard
guard and a footer with delete, cancel and submit. AreaModal and TagModal
now render through it. Project, task, goal and the other entities still
use their existing surfaces.

* feat(side-panel): project form uses the shared side panel

ProjectModal now renders in EntitySidePanel with every field visible,
instead of collapsible sections behind icon toggles. Goal changes count
as unsaved changes. The panel stays open while the delete confirmation
is showing.

* feat(side-panel): goal create and edit use the shared side panel

GoalModal replaces the inline goal form on the goal page and the
/goal/new route. The sidebar, the Goals page and the goal page open the
panel. A new goal still opens its page once saved. /goal/new now
redirects to /goals.

* feat(side-panel): person form uses the shared side panel

PersonModal renders in EntitySidePanel. The relationship picker is now a
select, so it no longer needs its own portal menu.

* feat(side-panel): member add and edit use the shared side panel

MemberModal and MemberEditModal render in EntitySidePanel. The panel now
falls back to English labels for Cancel when a translation is missing.

* feat(side-panel): habit create uses the shared side panel

HabitModal creates a habit in EntitySidePanel and reuses HabitSettings.
Habit edits stay inline on the habit page, which now only handles existing
habits. /habit/new redirects to /habits. Settings show the saved habit
directly instead of an optimistic draft.

* feat(side-panel): task core fields edit in the shared side panel

TaskModal edits name, description, status, priority, project, area, due
date and tags, and sends only the fields that changed. The task page header
gets an Edit details item. Recurrence, relations, subtasks and reminders
stay on the task page. Task creation stays in the capture box.

* style(inbox): soft dividers between inbox rows

Inbox rows get the same one-pixel line as task sheets. It drops away next
to the open item, which already sits apart from its neighbours.

* fix(inbox): keep row corners steady when an item opens

The divider border grew and shrank with the open item, which animated the
rounded corners of the rows beside it. The border now always takes up its
1px and only its colour changes.

* fix(inbox): stop rounded corners animating when an item opens

The row wrapper transitioned every property, so border radius animated
from square and flashed when an item opened. Only margin and padding
animate now; the corners switch at once.

* fix(inbox): open the item and its list state in one render

The list only learned an item was open from an effect after the card
rendered, so the card sat in a square row for a frame before its rounded
corners took over. The click now updates both in the same render.

* style(inbox): animate corner rounding with the open item

The row wrapper transitions every property again, so rounded corners ease
in and out with the spacing now that the open state changes in one render.

* fix(inbox): draw the open card on the row wrapper so corners animate together

The open card kept its own rounded corners, while the rows beside it changed
theirs over 300ms, so for a moment the corners did not match. The wrapper now
carries the rounding, background and shadow, so the open item and its
neighbours animate on the same element. The card inside is flat.

* ci(release): post new GitHub Releases to Discord (chrisvel#1852)

* ci(release): post new GitHub Releases to Discord

Adds an announce job after the release job in docker-publish.yml that
posts the release title, link and notes to the Discord webhook stored
in the DISCORD_WEBHOOK_URL secret.

* ci(release): add a release announcement line to the Discord post

* feat(ui): align goal, area, tag, person and view lists and details (chrisvel#1855)

Goal, area, tag, person and view lists become one paper list with a left
border. Goal, area, tag and view details get the shared header card, tabs
and project cards. Goals and areas are reorderable with per-user order
saved through new endpoints.

* release: v1.8.1

* fix(welcome): let the welcome video play under no-referrer policy (chrisvel#1856)

The app sends Referrer-Policy: no-referrer, and YouTube refuses to play
an embed without a referrer, showing a player configuration error. Set
the referrer policy on the welcome iframe only.

* release: v1.8.2

* fix(projects): show the project name in the delete confirmation (chrisvel#1857)

The delete dialog on the project page never passed projectName, so the
English message and the placeholder in other locales rendered the raw
{{projectName}} token. The English message also did not name the project
at all, so it now includes it.

* fix(search): draw the search dropdown above the sidebar (chrisvel#1858)

The navbar's z-30 stacking context trapped the search dropdown, so the
sidebar (z-40) covered part of it. Raise the navbar while search is open.

* feat(api): Focus scope params on list endpoints and ui_settings.focus (chrisvel#1861)

List endpoints accept area_uid, project_uid and tags as a scope: tasks (all
types, the Today lists and the upcoming projects block), daily plan and its
candidates, notes, projects and universal search. An area scope includes
tasks inherited through a project in the area and follows the user's own
placement of shared projects; a project outside the area, an unknown tag or
anything the user cannot see matches nothing. The plan keeps its items and
marks each in_focus instead of hiding them, so a drag or carry-over from the
client cannot drop them.

PUT /profile/ui-settings takes { focus } (area, optional project, up to ten
tags) and a profile save keeps it. The server never applies the saved focus
on its own, so API tokens, MCP and Telegram see the same lists as before.

Refs discussion #664

* feat(focus): Focus chip scopes the app and fills capture defaults (chrisvel#1862)

* feat(api): Focus scope params on list endpoints and ui_settings.focus

List endpoints accept area_uid, project_uid and tags as a scope: tasks (all
types, the Today lists and the upcoming projects block), daily plan and its
candidates, notes, projects and universal search. An area scope includes
tasks inherited through a project in the area and follows the user's own
placement of shared projects; a project outside the area, an unknown tag or
anything the user cannot see matches nothing. The plan keeps its items and
marks each in_focus instead of hiding them, so a drag or carry-over from the
client cannot drop them.

PUT /profile/ui-settings takes { focus } (area, optional project, up to ten
tags) and a profile save keeps it. The server never applies the saved focus
on its own, so API tokens, MCP and Telegram see the same lists as before.

Refs discussion #664

* feat(focus): Focus chip scopes the app and fills capture defaults

A Focus chip in the top bar picks an area, optionally a project inside it
and tags, saved per account in ui_settings.focus. While set, Today and the
planner, Upcoming, Tasks, Projects, Notes and live search show only that
scope; plan items outside it are dimmed, not dropped. The capture box gives
new tasks the focus project and tags when the text names none (a typed
+project or #tag wins), new notes the project and new projects the area; the
inbox-to-task conversion and the New project panel follow the same rule. A
focus pointing at a deleted area or project clears itself, and empty lists
say which focus they are showing with a link to clear it.

Closes discussion #664

* style(focus): pick area and project with the app's dropdown instead of native selects

FilterDropdown gains a fullWidth mode for forms and popovers.

* feat(navbar): show the logo icon on phones while the sidebar is collapsed

* release: v1.9.0

* i18n(focus): translate the Focus chip in all locales (chrisvel#1864)

* fix(focus): scrollable, searchable, open-projects-only menus in the Focus popover (chrisvel#1865)

* fix(focus): keep long area and project menus in the Focus popover scrollable

* fix(focus): offer only open projects in the Focus popover

* feat(focus): searchable area and project menus in the Focus popover

The dropdown gains a searchable mode with a search box at the top of the
menu. The locale cache version is bumped so browsers fetch the translations
that carry the focus keys.

* release: v1.9.1

* feat(tags): New Tag button and admin empty-account stats (chrisvel#1866)

* feat(tags): New Tag button in the Tags page header

Matches the New button on the other entity pages (NewItemButton), opening the tag modal in create mode. Adds the tags.new key to every locale.

* feat(admin): count accounts that never created anything

Shows a Nothing created stat on the admin dashboard and an Activity filter in the admin user list.

* fix(admin): drop unfinished visits code from the empty-accounts commit

The previous commit swept in another session's in-progress visits changes, which reference a service that is not committed and broke the admin overview in CI.

* fix(billing): cache the plan badge so the user menu row does not lag

PlanBadge mounted on each menu open and rendered nothing until the billing request returned. It now starts from the last known status and refreshes in the background.

* fix(mcp): accept tags sent as a JSON array string (chrisvel#1860) (chrisvel#1867)

Some MCP clients serialize array arguments to a JSON string, which the tag resolver rejected. It now decodes a string holding a JSON array; other strings are still rejected.

* fix(capture): link a project page's task to that project by uid (chrisvel#1868)

The project page opened the capture box with +Name, and the box matched
that name against all projects. With two projects sharing a name, or a
name missing from the store, the task landed in the wrong project or in
the Focus project. The box now receives the page's project uid and links
tasks and notes to it when the text has no +project or names that same
project.

* fix(ios): draw under the notch and tint the status bar to match the top bar (chrisvel#1870)

Add viewport-fit=cover so the page reaches the notch area, pad the page
panel by the top safe-area inset so the header clears the clock, and set
light and dark theme-color so the status bar matches the header instead
of showing a white rectangle. Fixes chrisvel#1869

* fix(tasks): use groupedByKey in merged list container class

The upstream sync merged upstream's new container className check
(groupBy === 'project' && groupedByProject) next to the fork's rename of
groupedByProject to groupedByKey, which also covers area grouping. The
frontend build failed with TS2304. Area grouping now gets the same
spaced container as project grouping.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: danathar-atomic-hive[bot] <320561496+danathar-atomic-hive[bot]@users.noreply.github.com>

---------

Signed-off-by: danathar-atomic-hive[bot] <320561496+danathar-atomic-hive[bot]@users.noreply.github.com>
Co-authored-by: Chris <chrisvel@users.noreply.github.com>
Co-authored-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
Co-authored-by: Peter Gonda <pipo@pipoline.com>
Co-authored-by: Patrick Motard <motard19@gmail.com>
Co-authored-by: Chris Veleris <chrisveleris@gmail.com>
Co-authored-by: github-actions[bot] <41898299+github-actions[bot]@users.noreply.github.com>
Co-authored-by: danathar-atomic-hive[bot] <320561496+danathar-atomic-hive[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant