Skip to content

Release 2026.27 - #323

Merged
novoj merged 11 commits into
mainfrom
dev
Nov 2, 2026
Merged

novoj merged 11 commits into
mainfrom
dev

Conversation

@novoj

@novoj novoj commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

No description provided.

JNO and others added 11 commits November 2, 2026 09:00
Bitwarden offered saved logins on fields unrelated to signing in, for example
the search box of the agenda's task source picker.

Findings from Bitwarden's current browser-extension source:
- `data-bwignore` is honoured only when the user enables that option in
  Bitwarden; by default it is ignored.
- `type="search"` is excluded from login matching (ExcludedAutofillLoginTypes).
- `autocomplete="off"` is not an exclusion.

Changes:
- `getPasswordManagerAttributes()` (src/lib/password-manager.ts) emits
  `autocomplete="off"` plus the ignore markers of Bitwarden, 1Password,
  LastPass and Dashlane. `Input` and `Textarea` apply it by default; a
  credential autocomplete token (username, email, current-password,
  new-password, one-time-code) turns it off, so sign-in, registration,
  password reset, hosted admin re-auth and the Plaud e-mail and code keep
  working with password managers.
- Search fields (command palette, SearchCombobox and the pickers built on it,
  folder, recording, people, Almanac and task searches) are now
  `type="search"`; cmdk inputs render a search input through `asChild`. One
  base CSS rule removes the native search appearance, cancel button and
  decoration, so the fields look as before. The three inputs that were
  already `type="search"` lose the browser's native clear button.
- Non-login secrets (AI provider API key, pasted Plaud token) keep
  `autocomplete="new-password"` with the ignore markers, so a password manager
  never fills the site password into them.
- Raw text inputs that could not switch to `Input` spread the helper.
When the sign-in session ended while a page was open, the page kept showing
the old view and every action failed with its own confusing error, because
API routes answered 401 AUTH_SESSION_MISSING. With single sign-on the session
has a fixed lifetime (OIDC_SESSION_MAX_AGE, 24 h by default, no refresh), so
this happened every day to anyone who kept a tab open.

- A SessionWatcher in the signed-in app layout schedules a session check at
  the session's expiresAt and re-checks when the tab becomes visible or the
  browser comes back online (at most once a minute). Each check re-reads
  expiresAt; a check that returns an already-past expiresAt (clock skew) is
  retried after 30, 60 and 120 s. A network error or 5xx is never treated as
  expiry. There is no periodic polling.
- One shared window fetch observer inspects a clone of same-origin /api/
  responses with status 401 (not /api/auth/*) asynchronously, bounded to
  16 KiB and one second, and raises the prompt on code AUTH_SESSION_MISSING.
  Responses are returned to callers unchanged and undelayed;
  REAUTHENTICATION_REQUIRED, 403 and other codes are ignored.
- The prompt is a modal: "Your sign-in has expired" with "Sign in again"
  (a full page load to /login?returnTo=<current page>) and "Later". It
  closes again if a later check finds the session restored.
- toastApiError stays silent for a 401 AUTH_SESSION_MISSING response, so the
  modal replaces the per-action error toasts; every other error still shows.
- The login page accepts returnTo, validated as a same-origin relative path
  (no //, backslash, control characters or scheme), and honours it after
  password sign-in, for an already signed-in visitor, and through SSO, where
  the callback is an absolute URL on the app origin because better-auth
  rejects relative callbacks with a fragment or colon. When the provider
  cancels before better-auth reads the OAuth state, the destination is
  recovered from sessionStorage for the retry.

Session lifetime, disableSessionRefresh and the /api/v1 contract are
unchanged.
…one step

The earlier-task chooser in meeting preparation added the selected tasks one
request at a time. Every request refreshed the agenda behind the dialog, so
adding ten tasks took long and the page jumped once per task.

- Each person group (and "No assignee") has a three-state checkbox that selects
  or clears the group's tasks visible under the current search.
- POST /api/meetings/[id]/tasks accepts `taskIds` (up to 500, deduplicated)
  next to the single `taskId`. All ids are checked and added in one
  transaction under the agenda lock: one refused id refuses the batch and the
  error names the refused ids. Tasks already active in the agenda are skipped,
  a removed task is restored, and positions follow the selection order after
  the current last task.
- The dialog sends one request with one optimistic update and closes; on
  failure it keeps the selection and shows one error.
- The listing and the add share one eligibility condition, matching the item
  rule of getVisibleTask; personal tasks no longer appear in the chooser
  (they could never be added).
The newsletter field is a real e-mail field: Bitwarden counts "email" as a username keyword whatever the autocomplete value, so "off" did not reliably keep it away and only removed the browser's own e-mail autofill. The honeypot keeps the opt-out.
The login page always passed a validated returnTo (falling back to /dashboard) to SsoLogin, so the client's sessionStorage fallback never ran: after a cancellation better-auth redirects to /login?error=... without returnTo and the retry landed on the dashboard. The page now forwards returnTo only when the query has one; the redirect of a signed-in visitor still uses the validated destination.
The last-meeting snapshot re-inserts its tasks at positions 0..N-1 when the task source changes or the agenda is filed elsewhere. Added older tasks had moved to max+1, so they could tie or interleave with a larger re-snapshot. They start at OLDER_TASK_POSITION_BASE (100000) again, sequential in selection order, in the service and the optimistic update; agenda tasks order by position, then task id.
The fetch observer snapshotted its listeners before awaiting the request and notified only that snapshot, so a watcher mounted during the request missed an AUTH_SESSION_MISSING response. It now notifies the listeners registered when the response is inspected.
fix(ui): keep password managers off search, name and note fields
fix(meetings): select a person's older tasks at once and add them in one step
feat(auth): ask to sign in again when the session expires
@novoj
novoj merged commit 0b44878 into main Nov 2, 2026
12 checks passed
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