Sync with upstream - #73
Merged
Merged
Conversation
Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com> (cherry picked from commit 7cf280694c7569cea0a2734923226163dff49aaa) Co-authored-by: Artyom Savchenko <armisav@gmail.com>
Adapted from intabia-fusion f475337671 (Hide notification groups). Ported by hand rather than cherry-picked: NotificationSettings.svelte has diverged from intabia's base, so the fix is re-applied against our current structure using the existing isDisabled() helper from @hcengineering/presentation. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Kristina Fefelova <kristin.fefelova@gmail.com>
Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
Ported from intabia-fusion (WebpushesPreferencesPresenter.svelte and related plugin/model plumbing), rewritten by hand against our current notification plugin rather than cherry-picked, since the source component never existed in this fork. - plugins/notification: new PushSubscriptionSetting class, name field on PushSubscription, WebpushesPreferencesPresenter component id, new IntlStrings. - models/notification: TPushSubscriptionSetting model, wired as the presenter for PushNotificationProvider. - notification-resources/utils.ts: parseUserAgent() helper, subscribePush() now records navigator.userAgent as PushSubscription.name, exported getPushPublicKey() for the settings screen's disabled-state checks. - New WebpushesPreferencesPresenter.svelte: list of a user's webpush subscriptions (device/browser label, per-device enable toggle, remove with confirmation, current-device indicator) + Subscribe button wired to the existing subscribePush(). Simplified vs. intabia's version: our subscribePush() returns a plain boolean rather than a granular error union, so the failure path shows one generic PushSubscribeError message instead of per-cause messages (permission denied / network / not supported). That granularity can be added later by extending subscribePush()'s return type. - en/ru locale strings for the new UI. Verified: tsc --noEmit clean for plugins/notification, models/notification and notification-resources/src (only pre-existing, unrelated presentation-package errors remain). Full svelte-check of the .svelte file was not completed in this session (workspace-wide svelte-check on this monorepo exceeds the available command timeout) - recommended follow-up before merge. This unblocks (fully or partially, still needs individual review) the 8 group-2.1 webpush bugfix commits that targeted this screen: 545279381f, 1f502582ea, 0096aaabed, 0f64f1cda9, 2b9ec98816, b90ecd98a1, 844f057b8d, 0d6169c9cc. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Elena Ostapenko <lunopad@gmail.com>
… is removed Adapted from intabia-fusion 360f67af4a (Fix inbox selection on delete). Not a direct port: intabia's InboxGroupedListView uses 'clear' semantics and a different displayData/selection model than ours (which has archive/unarchive). Ported only the underlying idea - track the last known-valid selection index (stableIndex) instead of recomputing highlightIndex via findIndex every render, which briefly returns -1 (and drops the highlight) for one render after the selected context is filtered out of displayData, before the parent prop updates. Also carries the one safely-portable piece of that commit as-is: ListView.svelte now passes the actual array item as a 'value' slot prop alongside the index (purely additive, no consumer is required to use it) so consumers like InboxGroupedListView can avoid re-deriving the item from a possibly-stale index. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Kristina Fefelova <kristin.fefelova@gmail.com>
…sion Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
Adapted from intabia-fusion da03810af4 (Fix common notification
pushes). That commit was mostly a props->intlParams rename across
several services (export/github/calendar-mailer) working around a bug
where CommonInboxNotification push content only read intlParams. We
don't have that bug: commonInboxNotificationToText() already merges
doc.props into params, so push/inbox/email all already pick up props
correctly - confirmed by reading server-plugins/notification-resources/
src/index.ts. The rename is a no-op for us and was skipped.
What *was* a real, applicable bug: OnUserMeetingInvite in love-resources
built the notification with 'props: { name: "" }' - an empty string
that (since props is merged after content's own intlParams) blanked out
the {name} placeholder in love.string.InvitingYou ("{name} is asking
you to join"), so meeting-invite notifications always showed a blank
inviter name. Fixed by resolving the actual sender name via
formatName(senderInfo.person.name, ...), same helper used by
getSenderName() elsewhere in the notification pipeline.
The request-resources part of that commit (propsIntl on
sendRequestCreateNotification/removeRequestCreateNotification) doesn't
apply either - those functions don't exist in this form in our fork.
Signed-off-by: Artyom Savchenko <armisav@gmail.com>
Co-authored-by: Kristina Fefelova <kristin.fefelova@gmail.com>
Adapted from intabia-fusion 8b7182d321 (Fix notification translate). Real bug in translate()/translateCB(): the whole function body was one try/catch funnelling every error - including IntlMessageFormat#format() throwing because of missing/invalid params - into handleIntlPipelineFailure(), which caches a permanent Status failure for that message. So one call with bad params (e.g. a notification whose intlParams were missing a placeholder) would mark the message as untranslatable forever, breaking every subsequent (valid) call for the rest of the session/until reload. Fix: format() failures are now caught separately and just fall back to returning the raw message for that one call, without touching the compiled-message cache. Compile/lookup failures (bad id, no loader, etc.) still go through handleIntlPipelineFailure and get cached as before - that part of the behavior is intentional and unchanged. Ported the two regression tests from the same commit (cache-poisoning for translate and translateCB) as-is - they matched our test file's existing structure. Full package test suite (31 tests) passes. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Kristina Fefelova <kristin.fefelova@gmail.com>
Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
… on thread replies Adapted from intabia-fusion 2b9ec98816 (Fix push notifications links). Ported the server-side (push.ts) part only - the desktop/Electron IPC side (desktop/src/ui/notifications.ts) has the same underlying bug but is left for separate follow-up since it needs the equivalent fix mirrored into a different file/function and I want to keep this change narrowly scoped and verifiable. Bug: createPushFromInbox() built the push notification's link straight from n.objectId/n.objectClass. For a reply inside a thread, those point at the ThreadMessage itself, which isn't a directly navigable page - clicking the push notification for a thread reply would fail to resolve to a real page/would land on the wrong document. Fix: added getObjectIdentity(), which detects the thread-reply case (attachedToClass derived from chunter.class.ThreadMessage) and resolves to the ThreadMessage's own objectId/objectClass (the document the thread is actually attached to) instead. Non-thread notifications are unaffected (identity passthrough). Verified: tsc --noEmit and eslint clean for server-plugins/notification-resources. No existing test suite in this package to run (none exists). Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Kristina Fefelova <kristin.fefelova@gmail.com>
…ribe errors
Two related intabia-fusion fixes landed together in this commit (staged
files got combined by an earlier scripting mistake on my end - not
worth unpicking into two commits after the fact, both are verified and
intentional):
1. packages/ui/src/tooltips.ts - adapted from 0f64f1cda9 (Fix creating
webpush subscription after deleting). The webpush settings screen
uses use:tooltip={{ label: disabledReason }} where disabledReason
starts undefined and later becomes a string reactively. The old
tooltip() action had an early return with a
"TODO: Fix reactive options update in this case" when initial
options had no label/component, so it never attached the
mousemove/mouseleave listeners - a tooltip starting empty and later
getting content would silently never show. Fixed by always
returning the action object and attaching/detaching listeners in
update() based on content transitions. This is a general fix to a
shared component, not webpush-specific.
2. plugins/notification(-resources)/*, notification-assets/lang/*.json -
adapted from b90ecd98a1 (Push subscription errors for Edge).
Edge doesn't implicitly prompt for notification permission from
pushManager.subscribe() the way Chrome/Firefox do, so subscribing
could silently fail there - fixed by calling
Notification.requestPermission() explicitly first. Also upgrades
subscribePush()'s return type from boolean to
'success' | 'permission_denied' | 'network_error' | 'not_supported'
so the settings screen can show the actual failure reason instead
of one generic message (this was called out as a follow-up in the
original webpush-screen commit). Updated all three call sites:
WebpushesPreferencesPresenter.svelte (shows the specific error),
BrowserNotificatator.svelte (was doing `if (isSubscribed)` on the
old boolean - every string in the new union is truthy, so this had
to become an explicit `=== 'success'` check), and
Notification.svelte (unaffected - never inspected the return value).
Verified: tsc --noEmit and eslint clean for packages/ui,
plugins/notification, plugins/notification-resources (only
pre-existing, unrelated presentation-package errors remain).
Signed-off-by: Artyom Savchenko <armisav@gmail.com>
Co-authored-by: Elena Ostapenko <lunopad@gmail.com>
Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
Storage (account-service): - Postgres migration v28: workspace_members.has_unread BOOLEAN DEFAULT FALSE. - AccountDB.setWorkspaceMemberUnread(account, workspace, hasUnread), implemented for both Postgres and Mongo backends. - getAccountWorkspaces() now selects/returns the flag as WorkspaceInfoWithStatus.hasUnread (new optional field on the core interface). RPC surface: - New account-service method setWorkspaceMemberUnread, exposed via utils.ts/operations.ts following the updateWorkspaceRole pattern. Authorization: raising the flag (hasUnread=true) for any account, or clearing someone else's, requires a 'notification' service token; clearing your own flag is self-service (no special privileges needed). - AccountClient.setWorkspaceMemberUnread added to @hcengineering/account-client. Trigger: - New OnInboxNotificationCreate trigger in server-plugins/notification-resources (registered in models/server-notification), fires on every InboxNotification creation independent of push-notification settings. Reports hasUnread=true to account-service for each receiver using a workspace-scoped service token (generateToken + getAccountClient, mirroring the calendar-resources pattern for calling out to external services from an in-process trigger). Best-effort: failures are logged, never block notification delivery. - Added @hcengineering/server-client and @hcengineering/server-token as dependencies of server-plugins/notification-resources. Not yet done (tracked in NOTIFICATIONS_PORT_PLAN.md): client-side self-clear call when a user's local unread count in a workspace hits zero, and the frontend badge in SelectWorkspaceMenu.svelte. Verified with tsc --noEmit on: server/account, foundations/core/packages/core, foundations/core/packages/account-client, server-plugins/notification-resources, server-plugins/notification, models/server-notification. No live account-service/ Postgres available in this sandbox to integration-test the migration or RPC path. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
- SelectWorkspaceMenu.svelte: renders a small NotifyMarker dot next to a workspace's name when WorkspaceInfoWithStatus.hasUnread is true and it's not the currently open workspace. - Workbench.svelte: when the local inbox transitions from "has unread" to "all read" (existing HasInboxNotifications reactive check), calls the new reportWorkspaceRead() helper to self-clear the flag for this account+workspace. - workbench-resources/utils.ts: reportWorkspaceRead() — self-service call to AccountClient.setWorkspaceMemberUnread(myAccountUuid, false) using the existing login.metadata.AccountsUrl / presentation.metadata.Token, mirroring the existing logIn/logOut account-client usage in this file. Raising the flag remains server-side only (OnInboxNotificationCreate trigger, previous commit) — the client never sets it to true, only clears its own. Verified with tsc --noEmit + eslint on the touched .ts sources (0 errors). Svelte template changes reviewed by hand — svelte-check itself could not be run to completion in this sandbox (exceeds the available command timeout), so a full svelte-check pass before merge is still recommended. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
…mselves) Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
…/server-token deps The port added @hcengineering/server-client and @hcengineering/server-token to server-plugins/notification-resources/package.json but the shrinkwrap was not regenerated, so 'rush install' failed the shrinkwrap validation and the entire build job (and every job that needs it) errored out. Ran 'rush update' to add the two workspace links to the lockfile. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntWorkspaces expectation getAccountWorkspaces now returns a per-workspace hasUnread field, which broke the existing mongo unit test that asserted the raw workspace objects. Update the expectation and strengthen it to assert the true/false/default mapping. Add unit coverage for setWorkspaceMemberUnread authorization in both the mongo collection (flag write) and the RPC handler (utils): raising another member's flag requires the notification service token, a member may self-clear their own flag without it, no token can raise its own flag, and the write always targets the token's workspace rather than any caller-supplied value. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…atures, buttonDisabled) The formatting CI job flagged three pre-existing prettier violations in the ported code that were never run through 'rush format': the long setWorkspaceMemberUnread signatures in the mongo/postgres account collections and the buttonDisabled reactive statement in WebpushesPreferencesPresenter. Applied the repo format pipeline (prettier + eslint --fix) to just these files. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The port added 15 new notification strings (webpush subscribe/remove flow and
push error states) to en and ru only. Add translations for the other 12
locales (cs, de, es, fr, it, ja, ko, pl, pt, pt-br, tr, zh) so the web push
settings screen isn't left English-only there. Placeholders ({app}, {title},
{value}) and <b> markup preserved; Value is a passthrough.
Signed-off-by: Artyom Savchenko <armisav@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ace unread works The OnInboxNotificationCreate trigger runs inside the transactor (pods/server) and reports unread notifications to the account service via getAccountClient() from @hcengineering/server-client, which resolves the account URL from serverClient.metadata.Endpoint. The transactor never set that metadata (it only kept accountsUrl as a local for its own session login), so the RPC had no endpoint, failed, was swallowed by the trigger's fire-and-forget catch, and workspace_members.has_unread was never raised — the switcher dot never lit up. Set serverClient.metadata.Endpoint from config.accountsUrl at transactor startup (adding @hcengineering/server-client as a direct dependency). Also add a unit test for OnInboxNotificationCreate covering the behaviour the feature depends on: one account-service call per distinct receiver with the notification-scoped service token, skipping already-viewed notifications and the read-only guest, and swallowing account-service failures. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nsumer
Replaces the per-receiver account RPC fan-out from the notification trigger
with a queue-based path, so a channel-wide mention no longer becomes an RPC
storm against account-service (the shared chokepoint).
- server-core: new QueueTopic.WorkspaceMemberUnread + WorkspaceMemberUnreadMessage
{ accounts }; kafka registers the topic (2 partitions).
- trigger (OnInboxNotificationCreate): collapses a whole notification batch into
a single fire-and-forget produce keyed by workspace, instead of one account
RPC per receiver. Drops the server-client/server-token deps it no longer needs.
- account-service: consumes the topic on a stable group (work-queue semantics)
and raises the flag via a new bulk AccountDB.setWorkspaceMembersUnread, writing
the DB directly — no RPC or service token on the set path. Degrades quietly
when no QUEUE_CONFIG is present.
- account DB: setWorkspaceMembersUnread updates all members in one statement,
guarded by has_unread <> target so a repeat broadcast writes nothing.
- Revert the transactor serverClient.metadata.Endpoint wiring: the trigger no
longer calls the account RPC, so it is no longer needed.
Client self-clear (single-account RPC) is unchanged. Unit tests cover the
trigger producer, the consumer handler, and the bulk DB method.
Signed-off-by: Artyom Savchenko <armisav@gmail.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…:^0.7.18 rush check requires one consistent version specifier per dependency across the monorepo. The whole repo pins @hcengineering/kafka at workspace:^0.7.18; I had added it to account-service as ^0.7.20 (its package version), which tripped 'Found 1 mis-matching dependencies' in the rush check gate and failed build + all uitest* jobs before the app stack even started. Align to ^0.7.18. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n, workbench and desktop Makes the cross-workspace unread indicator a first-class, live signal instead of a load-time snapshot only visible inside the open switcher. Foundation (workbench utils): - refreshWorkspaces() re-fetches workspacesStore (incl. per-workspace hasUnread); Workbench polls it (~45s) and on window focus/visibility, since the flag is raised server-side in workspaces the client isn't connected to. - hasCrossWorkspaceUnread derived store: single source of truth (any workspace other than the current has unread). - reportWorkspaceRead() now optimistically clears the current workspace's flag locally so switching away and back is instant. Surfaces: - Change workspace / already logged in: covered by the switcher's on-open refetch plus the new focus + interval refresh. - Login SelectWorkspace: a dot on each workspace that has unread. - Working in a workspace: a NotifyMarker on the workbench workspace logo whenever another workspace has unread (always visible, not just in the open switcher). - Desktop: the dock/taskbar/tray badge folds in cross-workspace unread as one extra count (same additive pattern as in-workspace + communication unread), so the app icon lights up even when the current workspace is fully read. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…o-op flag writes Review follow-up: - Self-clear (reportWorkspaceRead) previously fired only on the has-unread -> all-read transition, so a session that loaded already-read left a stale-true server flag (e.g. items read on another device) sticky until something else cleared it. Now it also clears once on load when the local inbox is empty-and-read, tracked by a flag so it fires at most once per read state. - Guard the single-account setWorkspaceMemberUnread with 'has_unread <> target' (postgres) / hasUnread $ne (mongo) so the on-load clear is a no-op write when the flag is already in the desired state, matching the bulk variant. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Pause the 45s workspacesStore poll while document.hidden; the visibilitychange/focus handler already refreshes on return, so backgrounded tabs stop hitting GetWorkspaces. - Drop the hardcoded English title on the login unread dot (i18n gap); the dot keeps NotifyMarker's color token, so no visual inconsistency. - Document that the raise path now goes through the WorkspaceMemberUnread queue, so setWorkspaceMemberUnread's service-token branch is a guarded fallback rather than the normal raise path (kept intentionally to keep the endpoint safe). Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… for diagnosis The trigger only logged on error, making the producer half a black box. Add an info log when the trigger publishes to the WorkspaceMemberUnread queue (with workspace + receiver count) and a warn when control.queue is unexpectedly absent, plus an info in the account-service consumer when it applies a batch. Lets an operator confirm end to end where the chain breaks (produce vs consume). Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…opic The cross-workspace unread topic is produced by region-specific transactors (REGION=<region>) but consumed by the single global account-service (no REGION). getKafkaTopicId prefixes topics with the producer's region, so the transactor published to 'cockroach.workspace-member-unread' while account-service consumed the bare 'workspace-member-unread' — the message was never delivered and has_unread was never raised (producer logged 'published', consumer never 'applied'). Mark WorkspaceMemberUnread as a global topic so both ends resolve to the same name regardless of region. Postfix (staging/prod) is preserved. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the unread dot subtler in the workspace switcher list and on the collapsed workspace logo (x-small -> xx-small), kept consistent across both. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xed orange The fallback initial box in the workspace rail logo was hardcoded to one brand orange for every workspace, while the Settings workspace avatar generates its color from the name (AvatarType.COLOR) — so the same workspace looked orange in the rail but, say, blue in settings. Generate the rail box color from the workspace name via getPlatformColorForText so both match. Uploaded custom icons are unaffected. (Unrelated to the cross-workspace notifications work; small consistency fix.) Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nto the logo Now that workspace logos are coloured by name, a red unread dot could sit on a red logo and disappear. Add a background-coloured ring around the marker on the workspace logo (standard avatar-badge cutout) so it stays visible on any logo colour. Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ocks The module-level `globalTopics` Set referenced QueueTopic at import time, which failed under a circular import (server-core's enum not yet initialized) with "Cannot read properties of undefined (reading 'WorkspaceMemberUnread')". Replace it with an isGlobalTopic() function that reads the enum at call time. Also add the info/warn stubs to the trigger and consumer ctx test mocks so the new diagnostic logging in push.ts and unread.ts no longer throws "ctx.info is not a function" / "control.ctx.warn is not a function". Signed-off-by: Artyom Savchenko <armisav@gmail.com> Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web-push settings screen writes PushSubscriptionSetting.enabled per device, but the server push path gathered subscriptions filtered only by receiver and never joined against the setting — so toggling a device off was a no-op and pushes kept arriving. Filter out subscriptions whose setting has enabled === false in PushNotificationsHandler (absence of a setting means enabled). Extract the join as a pure filterEnabledSubscriptions() helper and unit-test it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012NwjLAe9GVw1QeYjkhu72R Signed-off-by: Artyom Savchenko <armisav@gmail.com>
Notification fixes + cross-workspace unread indicator
Signed-off-by: Artem Savchenko <armisav@gmail.com>
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.