Skip to content

Apps: promote stage -> stage-apps (stage prereleases, Node 26.12.1) - #11335

Merged
evereq merged 2583 commits into
stage-appsfrom
stage
Oct 23, 2026
Merged

evereq merged 2583 commits into
stage-appsfrom
stage

Conversation

@evereq

@evereq evereq commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

PR

Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.

  • Have you followed the contributing guidelines?
  • Have you explained what your changes do, and why they add value to the Platform?

Promotes stage → stage-apps to build the six Electron app prereleases (desktop, desktop-timer, server, server-api, server-mcp, agent) at the stage version just released by #11332 (owner request: "cascade to Stage and to Stage Apps … so it builds desktop apps pre-releases").

Merged as a merge commit: each *-stage.yml app workflow's check-release-tag gate resolves the release tag on HEAD^2 (the merged stage tip, tagged by Release Stage), as on the earlier promotions (#10257, #10469). stage-apps carries no code of its own (only promotion merges), so the content is exactly stage.

🤖 Generated with Claude Code


Summary by cubic

Promotes the stage branch into stage-apps so the six Electron app prerelease workflows (desktop, desktop-timer, server, server-api, server-mcp, agent) build at the stage version just released. The merge commit makes each *-stage.yml app workflow's check-release-tag gate resolve the release tag on HEAD^2 (the merged stage tip), as earlier promotions did. stage-apps carries no code of its own, so the content is exactly stage.

Written for commit 46563b1. Summary will update on new commits.

View guided diff Turn on auto-fix

evereq and others added 30 commits October 18, 2026 14:21
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
perf(core): response compression, single-flight role-state load, PermissionGuard from the attached state, /public max-age
… entrypoint rewrites

Review of #11070 (CodeRabbit, Greptile): `add_header ... always` also put the
one-year immutable header on the 404 a bundle missing during a rolling deploy
answers, and both entrypoints substitute environment values (API_BASE_URL, ...)
into the built *.js at container start, so those bundles' bytes change while
their hashed name does not — a year of immutable caching would pin the old
values. The entrypoints now checksum *.js before and after the substitution and
write `map $uri $bundle_cache_control` (changed bundles → `public, no-cache`,
revalidated by ETag on every load; the rest → one year immutable), which both
templates include from the http block; the bundle location uses that value
without `always`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
perf(webapp): gzip responses and cache the hashed bundles for a year in nginx
Release: develop → stage (compile fixes, unit-run fixes, perf caching, webapp gzip, SDK rc.7, inbox GraphQL, Ever Works integration)
* feat(i18n): add PROJECT_QUICK_LOOK strings for the project quick-look drawer

* feat(projects): describe a project's details, plan and billing for the quick-look drawer

* feat(projects): add the project quick-look drawer with progress, tasks, daily activity and top contributors

* feat(employees): register the project quick look in the employees module

* feat(employees): open and close a project's quick look from the employee page

* feat(employees): make the Projects panel cards open the project quick look

* fix(projects): address review on the project quick look

- Clear the previous project's hours, contributors and top tasks when another
  project opens, so its figures never show under the new project's name.
- Show the hours budget bar only when the statistics read counts everyone's
  time (CHANGE_SELECTED_EMPLOYEE, an org-wide viewer with no employee record,
  or a manager of the project); anyone else gets back only their own time.
- Leave Visibility blank until the project's `public` flag is known, instead
  of showing "Private" for the card the drawer opens with.
- Mark the `closed` output readonly, per the repository's Angular guide.

* fix(projects): count only an active, unarchived manager membership for the budget bar

The server reads a project manager's membership only while it is in effect
(isActive true or unset, isArchived false or unset). Match that in
canReadProjectTotal, so an inactive or archived manager no longer sees their
own hours shown as the project's budget usage.

* fix(employees): keep the project quick look open when its card drops out

A project the employee is not a member of is listed in the Projects panel
only while they have time on it in the period. Switching to a period with
none removed its row, so viewedProjectRow returned null: the drawer reset
and closed by itself while viewedProjectId stayed set, and switching back
reopened it unasked. Fall back to the card the drawer was opened from with
no time (one object while the drawer is open, so the input stays stable),
as the team page does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(projects): caption the quick look's contributors by whose time they are

The employee page reads the period figures with no employee filter, which
the server narrows to the caller's own time unless they may select any
employee, are an organization-wide viewer without an employee record, or
manage the project. The caption still said "Everyone's time" for such a
caller. With no caption from the host, use the same canReadProjectTotal
check the budget bar uses and say "Your time on this project" otherwise
(new PROJECT_QUICK_LOOK.YOUR_TIME_CAPTION in every locale).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(projects): caption a team-scoped quick look as the team's time

With no caption from the host, a viewer who may read the project's totals
saw "Everyone's time" even when the host scoped the figures to a team's
members (CodeRabbit on #11041). Use TEAM_CONTRIBUTORS_CAPTION when
scopeEmployeeIds is set; "Your time" stays for a viewer the server limits
to their own time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(projects): let the contributor rows widen the quick look's caption

The drawer starts the period reads before the project's full read lands,
so a project manager could already see everyone's rows while
canReadProjectTotal, run on the card without members, still said "Your
time" (CodeRabbit on #11041). Say "Your time" only when the rows hold no
one but the viewer too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Ruslan Konviser <evereq@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Release: develop → stage (employee project drawer #11041)
…webhook is never orphaned, a key of another integration is refused (review F57, F58, F60)

- F57: a 401 health check on a connection an admin paused keeps it PAUSED (the
  admin still gets the needs_reauth notice once), so the next key rotation no
  longer restarts the sync the admin stopped.
- F58: disconnect forgets only the subscription it deleted; one a registration
  stored meanwhile is deleted too (best effort) or keeps its id for the
  reconnect to delete.
- F60: connect and rotate refuse a key whose whoami integrationKind is not
  ever-gauzy (Ever Works would refuse every link write of this plugin);
  validate answers the kind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… phase or mapping, a resync re-applies skipped records, outbound retries span two hours (review F72, F73, F74)

- F72: a phase that stops importing part-way (direction off, scope gone)
  releases what it held back at once; the sweep cancels a run whose mapping
  was removed (or moved to another connection) and releases its held records.
- F73: a resync that reads a record an earlier run skipped or dead-lettered
  (same event id) queues it again instead of counting a duplicate.
- F74: the outbound back-off is a minute doubling to an hour, so the eight
  attempts span about two hours instead of about four minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e registration's row lock; its answer covers a raced subscription (#11074 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ms and projects stop their pairs, unsynced resources keep their scope (review F75, F76, F78, F79, F80)

- F75: team.member.*, team.resource.* and work.member.* are skipped only by
  their own child's version (or a full state as new), never by the parent
  version another child's event moved on; an older child event never lowers
  the link's version.
- F76: a team deleted in Gauzy unlinks its pair on its next member/resource
  event or sweep instead of dead-lettering; the team and project sweeps go on
  past a link they cannot sweep; a project deleted in Gauzy is skipped.
- F78: team.resource.* changes the team scope only of a work or task whose
  pair syncs (OK) and whose Gauzy record exists.
- F79: an explicit People link wins over an accepted suggestion for either
  side, and a person suggested twice is left to the admin.
- F80: Create project from work replaces a pair that stopped syncing
  (UNLINKED) instead of doing nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… released in one transaction; the race spec reaches the ignored insert (#11075 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re completeOrder)

develop's schema check fails on f54443e: the generated snapshot has a blank
line between archiveOrder and the described completeOrder field, the committed
one does not. Whitespace only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a new task is linked before its later steps, team tags survive label changes, attribution stays in Gauzy (review F81, F82, F83, F84)

- F81: a task Gauzy created whose link was lost is linked back with no Gauzy
  side and its push queued; Ever Works' lossy copy (cut title, create status,
  cut labels) is no longer written over the Gauzy task.
- F82: a new Ever Works task is linked right after the POST and again after
  each later step (transition, each set), so a refusal there never leaves it
  unlinked or behind Ever Works' version.
- F83: an Ever Works label change keeps the task's own tags (team-scoped ones
  included) for the names it has; only added names are resolved or created.
- F84: an edit of the Gauzy copy of a comment Gauzy attributed (an unmapped
  author) is sent without Gauzy's attribution line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chore(core): refresh the GraphQL schema snapshot (blank line before completeOrder)
…gestions are counted among those the explicit links leave open (#11076 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… found by the health check keeps an admin pause (#11074 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests once

On every load the app asked for the signed-in user and the role's permissions
two or three times with the same parameters: AppInitService.init(), then the
pages entry point again, then a route resolver. Each repeat was a full round
trip (/role-permissions/me ~164 KB) answered from the database again.

RecentAnswers<T> serves an identical request — in flight or answered within a
short window — the same promise. UsersService.getMe reuses an answer for 10 s
per (user, token, relations, includeEmployee) and every write of the service
forgets it (forgetMe() is public for other writers); PermissionsService
.getPermissions reuses one for 10 s per (user, token), forgetPermissions()
public. Callers are unchanged and still apply the rows to the store and
ngx-permissions on each call.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…orks edits and Gauzy moves; initiative write answers list their teams (review F85-F91)

- F85: while a goal's team change waits for its pairing, the Ever Works-only
  team lists stay too, so the next push never sends the old team plus the new.
- F86: a goal sample records the goal's version only when nothing else moved
  it; an idea dismiss/restore whose answer shows an edit made meanwhile applies
  that answer first (the moved status stays).
- F87: a retry that finds the goal or initiative it wrote (stable id) gives it
  Ever Works' current fields before the snapshot is recorded.
- F88: a criterion or DoD item re-added with the same id is restored (not
  re-linked while archived, which made the next push delete it).
- F89: a mission none of whose teams is paired waits (teams first) instead of
  becoming a TEAM-scoped initiative with no team; the tab refuses to create it.
- F90: a goal leaves its initiative when Ever Works clears the primary mission
  only if it is still on the old primary's initiative.
- F91 (core): initiative create and update answers list teamIds whether or not
  the write stated teams (the stored ones; none for a create without them).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(integration-ever-works): admin pause survives a 401; racing webhook never orphaned; key of another integration refused (review F57, F58, F60)
fix(integration-ever-works): held initial records released, resync re-applies skipped records, outbound retries span two hours (review F72, F73, F74)
…uzy values unsent; the attribution line matches escaped names and only Ever Works' own identical line (#11079 review)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le, cross-area pages and unpaired folders keep syncing, handled deletes are not re-queued; inbox items have one recipient (review F92, F93, F95, F96, F97)

- F92: a page edit the Markdown cannot show (an uploaded image, underline,
  colours) moves a stored editor-JSON hash; an Ever Works write over it then
  keeps the page's content as a version even inside the snapshot window.
- F93: Ever Works inbox items are upserted with replaceRecipients, so a member
  paired again stops reaching the person paired before.
- F95: a page moved between the organization documents and a Work (or two
  Works) keeps its title, description and body syncing; the move is logged
  once, and Ever Works edits no longer flag a false conflict.
- F96: a page in a library folder whose pairing ended goes to the next synced
  folder up (or Unfiled) instead of waiting forever.
- F97: the sweep no longer re-queues a deleted page whose archive went out, nor
  a deleted folder, every 10 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e, without optional chaining (DeepScan)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix(integration-ever-works): child events by child, deleted teams/projects stop their pairs, unsynced resources keep scope (review F75, F76, F78, F79, F80)
joel-kalema and others added 22 commits October 21, 2026 14:16
* fix(employees): size the Kind filter dropdown to match the header buttons

The global input-appearance floor forced the select to 38px with 13px text, beside 26px buttons with a 0.75rem bold label. Draw it to the buttons' box instead.

* feat(card-grid): accept per-column component inputs

The grid creates custom column components itself and never runs the table's componentInitFunction, so a page had no way to set the inputs it sets there.

* feat(card-grid): patch extra inputs onto custom cell components

* feat(card-grid): pass each column's component inputs to its cell

* fix(employees): open the employee profile view from the card grid

Clicking a name in the card grid went to the edit form; it now opens /pages/employees/view/:id, as the table does.

* chore: remove explanatory comments from employees dropdown and card-grid inputs

* fix(card-grid): re-apply component inputs on change and keep standard bindings authoritative

* fix(card-grid): reset custom inputs removed from the inputs record

* fix(card-grid): set declared component inputs through setInput so OnPush cells re-render
* feat(i18n): add the project form's section, logo and subtitle labels in every locale

New ORGANIZATIONS_PAGE.PROJECT_FORM keys for the redesigned Add and Edit project form. Acholi carries the English text until Crowdin translates it, as recent additions do.

* feat(projects): add tab state and keyboard navigation to the project form

Replaces the Nebular tabset's state with an activeTab and the list of tabs on offer: Sprints only for a saved sprint-mode project, Integrations and Automation only with a GitHub integration, Modules only for a saved project. Arrow, Home and End keys move between tabs (RTL-aware).

* feat(projects): redesign the project form with underline tabs, sections and an action bar

Main is grouped into Details, People and Timeline on a two-column grid, with the Add Organization logo tile in place of the 300px image block and its hard-coded 'Add or Drop Image' text. The form's panes are hidden rather than destroyed so the employee and team pickers keep their selection; Sprints and Modules load when first opened. Cancel and Save sit on the right of the action bar, Add Module and Manage Sprints on the left.

* style(projects): style the project form as a hairline panel

Underline tabs, sectioned two-column fields, toggles with their labels after the switch, a colour swatch inside the colour field and an action bar that sticks to the bottom of the page while the form is in view. Uses the shared gauzy-* and theme tokens only, so every theme follows.

* refactor(projects): drop the card and tabset modules the project form no longer uses

* style(projects): add shared heading and scroll styles for the project form pages

The body scroll rule is spelled 'nb-card nb-card-body' to outrank _pg-card's 'overflow: unset', which otherwise let the Documents panel spill out under the card.

* feat(projects): show the project name under the Edit Project title

Also drops the empty no-permission template and the large gap under the header.

* style(projects): use the shared project form page styles on Edit Project

* feat(projects): add a subtitle to the Add Project page

Also drops the empty no-permission template and the large gap under the header.

* style(projects): use the shared project form page styles on Add Project

* feat(docs-ui): redesign the record Documents panel

A plain section instead of a Nebular card: a compact header with a count badge and small ghost/outline actions, an empty state with an icon, and a divided list with a kind icon per row. Icon-only buttons get accessible names and the load error is announced.

* style(docs-ui): style the Documents panel as a hairline panel

Matches the project form: small title, count pill, tinted kind icons, rows that highlight their name on hover and a remove button that only turns red on hover. Uses the shared gauzy-* and theme tokens only.

* refactor(docs-ui): drop the card module the Documents panel no longer uses

* fix(contacts): flatten the Documents panel through its new header and body classes

The panel is no longer an nb-card, so the contact page's override now selects .docs-links-panel and its header and body.

* feat(projects): show the Documents panel in a column beside the Edit Project form

Both columns take the card body's full height and scroll on their own, so the page itself does not scroll. Below 1200px the panel drops under the form and the body scrolls them together.

* style(projects): scroll the open section inside the project form

Given a bounded height, the form keeps its tabs and action bar in view and scrolls only the open section, with a stable scrollbar gutter. The Documents panel likewise keeps its header and scrolls its list when a host caps its height. Without a bounded height both take their content's height as before.

* style(projects): compact the project form pages' header and let the form fill the body

Tighter header padding and no top padding on the body, so the form starts right under the title. On Add Project the form fills the body so its own sections scroll.

* feat(i18n): add the Documents panel's multi-file upload and multi-select attach strings

Upload hint, drop target, 'Uploading {{ current }} of {{ total }}…', the batch success and partial-failure toasts, and the attach dialog's selection count and 'Attach {{ count }}' button. 'Attach a document' becomes 'Attach documents'.

* feat(docs-ui): upload several files at once and accept dropped files in the Documents panel

Files upload one after another so a failure does not stop the rest, with a progress counter and a single summary toast; a single failed file keeps the server's own reason. Dropped files go through the same path, only for a host and user that may upload. Also reads a family and a short type label (PNG, PDF…) from each document's MIME type and name for the row icon and meta line.

* feat(docs-ui): redesign the Documents panel rows, header and empty state

Icon buttons with tooltips in the header so it fits a narrow column, whole-row open targets with a family icon and a 'PNG · 68 KB' meta line, hover actions, a dashed empty state that opens the file picker, an upload progress line and a drop overlay. The file input accepts several files and sits at the section level so both upload targets reach it.

* style(docs-ui): style the Documents panel's rows, family tints, progress and drop target

A tint per family (PDF, image, media, sheet, page), rows that highlight on hover and keep their actions visible on touch screens, a progress bar under the header and a dashed overlay while files are dragged over the panel.

* test(docs-ui): cover batch uploads, dropped files and document families in the Documents panel

* feat(docs-ui): attach several existing documents at once

The selection is a set that survives a new search. Each link is its own write, so one failing does not undo the others: the dialog closes with the links that were made and reports the rest, or stays open when nothing could be attached.

* feat(docs-ui): make the attach dialog a multiple choice

A checkbox per row, the selection count in the footer and an 'Attach {{ count }}' button.

* test(docs-ui): cover selecting and attaching several documents, with partial failures

* style(projects): let the Description editor use the project form's full width

The Description tab's section was capped at the 40rem of the single-field tabs. Long-form text reads better across the pane, so it gets a full-width section variant; Billing, Budget and Settings keep theirs.

* style(projects): put the project logo beside Name, Code and Project URL

The logo takes its own column spanning both field rows, with a label above it like the other fields so its top lines up with Name and its tile stretches to the bottom of Project URL. The upload hint moves into the tile's tooltip. Below 768px the logo stacks above the fields as a square.

* style(projects): widen the project form's side padding and align the tab bar and action bar to it

The open section's side padding goes from 1.5rem to 2.5rem, shared with the tab bar and the action bar so the first tab, the fields and the buttons line up. Below 768px all three drop to 1rem so the fields keep their width.

* style(docs-ui): make the Documents panel's attach and upload buttons a matching pair

Attach was a bare ghost icon and Upload an outlined button with a bright border. Both are now the same 2rem square with the panel's hairline frame and a muted icon, turning the primary colour on hover and focus; Upload fades while a batch is in flight.

* style(projects): lay out the Main tab as the logo on the left and every field on the right, two per row

The logo is a 10rem square in its own column, level with the Details heading and kept in view while the fields scroll. Details, People and Timeline stack in the right column, two fields per row: Owner moves into Details beside Project URL (client or internal describes the project itself), and Teams no longer spans the row. Below 768px the logo sits above the fields.

* feat(i18n): add the project form's setting card titles and descriptions in every locale

Open-source title, description, URL hint and 'Open repository'; descriptions for the Description, Tags, Billing, Budget, Appearance, Repository and both auto-sync cards; 'Public project' with what private projects hide; 'Sync by label'. Acholi carries the English text until Crowdin translates it, as recent additions do.

* feat(projects): link to the open-source repository once its URL is valid

The link is offered only while the project is open source and the URL passes the field's own validation, which accepts http(s) addresses only.

* feat(projects): present every tab but Main as setting cards

Each card has an icon, a title and what it is for, then either a switch on the right or its fields underneath, as on the SMS provider settings. Open-source opens its URL field inside its card and links to the repository; Settings splits into Appearance, Public project and Billable; Integrations shows the connected integration's logo; Sync by label opens its label field only while it is on. The Tags card names its field, so the input's own label is off.

* style(projects): style the project form's setting cards

The SMS provider cards' surface, hairline and type scale; a card body aligned with the title past the icon column, using logical padding for right-to-left languages; and switches centred in their row. Removes the switch-list, integration and narrow-section rules the cards replace.

* fix(projects): name the project form's switches and announce the repository URL error

NbToggle focuses an inner checkbox inside its own label, so aria-labelledby on the host never reached it and all five switches were unnamed. Each switch now projects its card's title, which names the checkbox, and the copy is hidden visually since the card shows it. The open-source URL input points aria-describedby at the error when the URL is invalid (the hint is not rendered then) and reports aria-invalid.

Also moves the plain declarations of .tab-row, .panes and .form-footer above their @media blocks (Sass mixed-decls); the output is unchanged.

* fix(docs-ui): name failed attachments picked before a later search

The selection kept only ids while names were looked up in the latest search page, so a document picked before another search was reported by its UUID when its link failed. The selection now keeps the picked documents themselves.

* fix(docs-ui): refuse file drops while an upload batch is running

The panel showed its drop target and accepted the drop, then uploadFiles discarded the files because a batch was in flight. Drops are now refused during a batch, matching the disabled upload buttons.

* fix(i18n): correct the Dutch repository description

'wiens' refers to people; the sentence now reads 'waarvan dit project de issues gebruikt'.
* feat(ui-core): add a CommentService client for the generic comment API

Loads a record's comments with their authors (oldest first) and posts new
ones through /api/comment, scoped by entity, entityId and the record's
tenant/organization. The author is never sent: the server takes it from the
request context.

* feat(i18n): add the empty state for an objective's comments

GOALS_PAGE.MESSAGE.NO_COMMENTS in every locale; ach, bg and he carry the
English text.

* feat(i18n): add the objective comment thread strings

GOALS_PAGE.COMMENT_THREAD: Reply, the message count, the empty thread, the
reply box placeholder and the fallback author name, in every locale; ach,
bg and he carry the English text.

* feat(goals): load, post and thread an objective's comments

The details dialog reads the objective's comments on open and groups them
into threads: each top-level comment with its replies, one level deep (a
reply to a reply joins its root's thread; an orphaned reply stands alone).
addComment() and addReply() post through CommentService, the reply with
parentId, then re-read the list since the create response has no author.
replyTo() opens a thread and focuses its reply box.

* fix(goals): make the objective dialog's comment box work, with replies

The comment box was markup only: an unbound textarea and a button with no
handler. It is now bound and posts the comment, and a Comments tab lists
them as messages, each with a Reply link and its replies in a thread card
that opens to the replies over a reply box. Ctrl/Cmd+Enter posts too.

* style(goals): quiet chat-style layout for objective comments

Small round avatars in a gutter, author and time on one line at the
dialog's 12px scale with light weights, and a thread card hung off the
avatar by a hairline stem and elbow.

* feat(i18n): add the objective comment delete strings

GOALS_PAGE.COMMENT_THREAD.DELETE_CONFIRM (the inline "Delete this
comment?" prompt) and DELETED (the success toast), in every locale; ach,
bg and he carry the English text.

* feat(goals): delete an objective comment or reply

canDelete() mirrors the server's rule (CommentService.assertMayManage): a
CHANGE_SELECTED_EMPLOYEE holder may delete any comment, anyone else only
their own, by author or, without an employee record, by createdByUserId.
deleteComment() deletes, closes the thread if it was open, re-reads the
list and toasts. Replies are kept: the database unlinks them (SET NULL).

* feat(goals): trash icon with an inline confirmation on comments

A bare trash icon beside Reply, and under each reply, shown only where the
server would allow the delete. It asks in place - "Delete this comment?
Cancel / Delete" - rather than opening a modal.

* style(goals): style the comment delete icon and its inline prompt

Muted icon that turns red on hover, the confirm action in red, and disabled
actions dimmed while the delete is in flight.

* fix(ui-core): read every page of a record's comments

getAll() stopped at the first 100 rows, and with the oldest first that left
out the newest comments once a record had more. It now reads page by page
(skip is a row offset on GET /comment) until the reported total, stopping
on an empty page.

* feat(i18n): add the objective comments load error

GOALS_PAGE.COMMENT_THREAD.LOAD_ERROR in every locale; ach, bg and he carry
the English text.

* fix(goals): keep drafts and surface failed comment reads

- A finished reply clears the shared reply box only if the user is still in
  that thread and the box still holds what was sent; the comment box gets
  the same value check, so a pending post never erases a newer draft.
- loadComments() resolves false on failure and flags commentsLoadError, so
  a failed read is not shown as an empty or stale list.
- timeAgo() reuses one Intl.RelativeTimeFormat per language.

* fix(goals): show a retry when the objective comments fail to load

The Comments tab shows the load error with a Retry button instead of
"No Comments yet" or the last list it had.

* fix(goals): restore the Updates tab status badge style

The comment styles had replaced the .update-status rule the Updates tab
still uses; it is back as it was, beside the style for the comments' load
error state.

* fix(goals): apply only the latest objective comments read

A comment and a reply can post at once, each refreshing the list, and the
reads can finish out of order. Each read is now numbered and only the
latest applies its list, error and loading state; an older one finishing
later is dropped.

* feat(i18n): translate the objective comment strings into Bulgarian and Hebrew

GOALS_PAGE.COMMENT_THREAD and GOALS_PAGE.MESSAGE.NO_COMMENTS. ach is left
as is: it is Crowdin's in-context pseudo-locale, filled by the Crowdin sync.
* feat(record-view): drawer takes an optional label for its full-page link

* feat(record-view): drawer spells out the full-page link's label beside its icon

* style(record-view): roomier padding for the drawer's labeled full-page link

* feat(projects): quick look can link tasks to the host page's task drawer

* feat(projects): quick look task links carry ?task= when the host has a drawer

* feat(projects): board cards open their task in the project page's drawer

* feat(projects): activity feed opens its tasks in the project page's drawer

* chore(projects): import the task view header for the project page's task drawer

* feat(projects): project page loads the task ?task= names into a drawer

* feat(projects): project page's task drawer, with a button on to the task page

* i18n(projects): task progress strings in English

* i18n(projects): task progress strings in the other languages

* feat(projects): task progress helpers — against the estimate, else the most-worked task

* feat(projects): board reads the time worked on its tasks for their progress

* feat(projects): board cards show their task's progress bar

* style(projects): board card progress bar, as the most worked tasks rows

* feat(projects): project page's task drawer reads the task's progress

* feat(projects): task drawer shows the task's progress under its header

* style(projects): board card progress bar keeps the most worked tasks green without an estimate

* style(projects): task drawer progress bar keeps the most worked tasks green without an estimate

* i18n(projects): who worked most on a task, in every language

* feat(projects): a task's bar fills to its share; who worked most on it, from its time logs

* docs(projects): board progress comments follow the bar filling to its share

* feat(projects): task drawer reads who worked most on the task

* feat(projects): task drawer lists who worked most on the task, as most worked tasks

* feat(record-view): a team row carries its members, for a list on hover

* feat(record-view): hovering a team chip lists its members

* style(record-view): team chip hover state and members popover

* feat(statistics): each employee's tracked time on tasks, summed by the server

* fix(projects): task drawer reads its top workers from server totals, not raw time logs

* fix(projects): project drawers load team members' employees, so their team chips list them

* fix(record-view): team member list is keyboard operable, with aria-expanded

* fix(statistics): a requested employee restriction that leaves no one reads no task workers

* fix(record-view): keyboard focus goes to the opened team's list, or the list itself without member buttons
* feat(i18n): add the project Daily activity year grid's labels

View switch names, the 'No time' legend band and the day tooltips ('{{ time }} on {{ date }}', 'No time on {{ date }}') under PROJECT_QUICK_LOOK, in every locale.

* feat(projects): build a year grid of the project's daily hours

On the project page, Daily activity can show the last twelve months, read once when the grid is first shown and again when the scope changes. Each month is a block of week columns; each day is shaded by fixed hours bands (none, under 1h, 1-4h, 4-8h, 8h+).

* feat(projects): switch Daily activity between its chart and the year grid

A chart/grid switch in the panel header on the project page; the grid names each weekday down its side and each month under its block, with a tooltip per day and a legend naming every band. The header's caption and total follow the shown view.

* style(projects): style the Daily activity year grid

Square days in shades of the chart's tracked colour, a wider gap between month blocks, and the view switch scaled to the panel header.

* feat(i18n): add the Daily activity year grid's year switch labels

'Last 12 months' and the earlier and later year buttons' names under PROJECT_QUICK_LOOK, in every locale.

* feat(projects): read the Daily activity year grid for any calendar year

Besides the last 12 months, the grid can show each calendar year back to the year the project started, or was created without a start. A calendar year lays out all twelve months and leaves the days to come blank, so its squares match. A reply for a year left behind never lands.

* feat(projects): add a year switch above the Daily activity year grid

Older on the left, newer on the right, the shown year between; each button is disabled at its end of the range.

* style(projects): style the Daily activity year switch

Small hairline step buttons around a fixed-width year label, so the switch keeps its place as the label changes.

* feat(projects): split the Daily activity year grid's time into tracked and manual

Each day carries how much of its square the manual fill takes (its manual share, at least a quarter when there is any), the year keeps its tracked and manual totals, and a day's tooltip names both parts when it has manual time.

* feat(projects): show manual time in the Daily activity year grid

The year's tracked and manual totals, with the manual share, beside the year switch; a manual fill on each day that has any; and a Manual entry in the legend.

* style(projects): style the Daily activity year grid's manual fill and split

Manual time fills a square from the bottom in the chart's manual colour, under the tracked shade; the split uses the chart legend's dots, and the legend's Manual entry is set apart from the hours bands.

* style(projects): compact the project page header

The global input floor drew the period switcher 46px tall, which set the header's height. It is now drawn to a 1.75rem box, and the header's vertical padding is tightened, on the project page only.

* fix(projects): guard the year grid's reads and keep older logged years reachable

readGrid returns early for an empty scope, as the period figures do: an empty employee filter would read everyone's hours. The oldest year starts from the earlier of the project's start and creation, and while the oldest year shown has any time, the year before it is offered too, so time logged before a moved start date stays reachable. Adds arrow-key, Home and End navigation between days, with one day in the tab order.

* fix(projects): expose each year grid day to keyboard and screen-reader users

The grid is a group of days, each named by its date and time and reachable with the arrow keys from a single tab stop. While a day has focus its figures show under the grid, since the tooltip opens on hover only. The grid sits in a wrapper that scrolls once the panel is too narrow.

* fix(projects): keep the year grid's days readable on narrow pages

Week columns never go below 6px: past the grid's minimum width it scrolls sideways instead of shrinking the squares. Adds a focus outline on the focused day and styles the focused day's readout.

* fix(projects): reach every logged year in the year grid across empty years

The oldest year offered no longer depends on whether the oldest year shown has time: one empty year hid every logged year before it. It now goes back to the earliest of the project's start, the project's creation and the organization's creation, since no time is logged before the organization existed.
…1215)

The MikroORM branch of the task statistics read `.rows` from the raw knex result, which only the PostgreSQL driver returns, so MySQL and SQLite always got an empty list.
The rows are now read from the shape each driver returns, and the PostgreSQL result is unchanged.
The members ranking and weekly hours joined every time log to its time slots and summed the log duration on each joined row, so a log weighed once per 10 minute slot. The slots are now checked with an EXISTS sub-query on both ORMs, so each log is summed once while the same members and filters are kept.
…t out (#11213)

* fix(time-off-policy): resolve employees given as ids or rows

The GraphQL resolver and the IEmployee[] contract send employees as { id } rows, but the lookup passed them straight into In(...), which only binds ids, and a missing list failed as not iterable, so those writes always answered 400. The lookup now takes ids or rows in both ORM branches, a create without employees saves a policy with no members, and an update without them keeps the current members.

* fix(time-off-policy): refuse employee entries without an id

A list such as [{}] used to resolve to no ids, so an update replaced the members with nothing instead of rejecting the input. Such an entry now answers 400, and an explicit empty list still clears the members.

* docs(time-off-policy): document the employee id helper
#11205)

* fix(channel-region): write the fallback region last when replacing a set

Replacing a channel's region set saved members in request order, so moving the fallback flag to a region listed before the current holder hit the fallback unique index and rolled back. The fallback member is now written after every other member, so the previous holder is demoted first, the same order setDefaultRegion already uses.

* test(channel-region): cover moving the fallback flag to an earlier member

Regression test for #11204: the double now refuses a save that would leave two live fallback rows (as UQ_channel_region_default does), so listing the new fallback before the current holder fails on the old loop and passes with this fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Ever Works AI <dev@ever.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(i18n): add the project activity feed's page range label

* feat(projects): page the project activity feed ten entries at a time

* feat(projects): add a pager under the project activity feed

* style(projects): style the project activity feed's pager

* refactor(projects): page the project activity feed with the shared list pager

* fix(projects): clear the activity feed's paged source when a reload starts

* style(projects): format the activity feed component with Prettier

Wraps the @angular/core import past the print width and drops a doubled blank line in the stylesheet; no code change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Ever Works AI <dev@ever.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* chore(deps): bump markdown-it from 14.3.2 to 14.3.3

Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 14.3.2 to 14.3.3.
- [Changelog](https://github.com/markdown-it/markdown-it/blob/14.3.3/CHANGELOG.md)
- [Commits](markdown-it/markdown-it@14.3.2...14.3.3)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-version: 14.3.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(deps): keep @cypress/request on form-data 4.0.6

The regenerated lockfile moved `form-data@~4.0.4` (required by
`@cypress/request`) onto the 2.5.6 entry, because the
`**/request/form-data` resolution glob also matches
`@cypress/request`. Restore develop's mapping so this PR only bumps
markdown-it 14.3.2 -> 14.3.3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ever Works AI <dev@ever.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(time-off): remove document when request URL is cleared

* fix(file-uploader-input): emit a checked URL only if it is still the input's value

A URL check that is still waiting for the image could finish after the field was cleared and put the removed URL back (flagged by CodeRabbit and Greptile). Clearing still emits at once.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(time-off): drop the document id when the request URL changes

A URL typed over an uploaded document kept the old documentId, and TimeOffRequestSubscriber could put that asset's URL back on reload (CodeRabbit). Browse uploads set both fields through uploadDocumentAsset and never reach this handler, so they keep their id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Ever Works AI <dev@ever.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* fix(graphql): resume a connection at a gone cursor row's position

When the row behind an after or before cursor had been deleted or filtered out, the fallback compared the cursor's ISO text with the row's Date and ignored the sort direction, so the walk restarted at the first row or answered an empty page. The fallback now reads the cursor value on the row's scale and follows the sort direction with the id tie-break, as the sort itself does.

* fix(graphql): keep rows tied on the first sort key at a gone cursor

The id only orders first-key ties when the sort has one key or its second key is the id in the same direction. For longer sorts a gone cursor now resumes at the start of the tie group (after) or ends the window after it (before), so rows may repeat but are never skipped.

* fix(graphql): place a gone cursor by the id in any direction and keep empty text rows

A second sort key of id orders first-key ties in its own direction, so the
fallback now uses it even when it differs from the first key's direction.

A cursor spells a null and an empty text alike. On a text column the fallback
now reads the side that repeats rows instead of skipping them: the earlier
position for after, the later one for before.

* fix(graphql): read an empty gone cursor as null on declared non-text fields

A field declared as a date, number, decimal or boolean never holds empty text,
so its empty cursor sort value is a null and the id places the null group
exactly. Undeclared fields still follow the rows' own values.

* test(graphql): cover resuming a cursor whose row is gone

after/before a deleted row answer the rows on either side of it, for createdAt DESC + id DESC, createdAt ASC, and createdAt DESC + id ASC. All three fail on the old fallback (after restarts at row 0, before answers an empty page).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(graphql): read a sort-only field's kind from its rows when a cursor's row is gone

A field that is sortable but not filterable has no declared kind, so an empty cursor value was read as text and the walk could repeat rows from the side the client already has (CodeRabbit). A Date, number or boolean on any row means the column is not text, so the empty value is the null and the id places it exactly; a field with no such value keeps the text reading, which repeats but never skips. Reachable today on contactBuyers (spendingLimit, periodSpendingLimit, approvalThreshold) and contactGroups (discountPercent). Adds null-tie cases for a declared DATE field and for a sortable-only numeric field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Ever Works AI <dev@ever.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Pin every exact Node 26 reference to 26.12.1, the 2026-10-21 security
release: .nvmrc, node:26.12.1-alpine3.24 in all api, worker, webapp,
mcp and mcp-auth Dockerfile stages, and setup-node in the agent,
desktop, desktop-timer, server, server-api and server-mcp workflows.
Also bump the Codacy CLI runtime node@22.2.0 to node@22.23.4 (same
major). Floating node:26 / node-version: 26 refs are left as they are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Task: T-61

Ever-Works-Node: everdesk1 (d6e766c2-c2af-4dbd-91fd-61df5bcbfe80)
Ever-Works-Agent: Senior Dev EVERDESK1 (50a8cd2f-3f63-4093-9b96-046c2700d301)
Ever-Works-Job: a47ab04b-2d66-414c-b205-25eebe9daa0c
Ever-Works-Run: a216739f-61f6-44e5-bc3e-8f9ee6b37b52
build(node): bump Node.js 26.11.1 to 26.12.1 security release
…8e89f218c00c

Task T-61: Daily self-build maintenance � Ever Gauzy (EVERDESK1)
* chore(deps): bump sanitize-html from 2.18.0 to 2.18.1

Bumps [sanitize-html](https://github.com/apostrophecms/apostrophe/tree/HEAD/packages/sanitize-html) from 2.18.0 to 2.18.1.
- [Changelog](https://github.com/apostrophecms/apostrophe/blob/main/packages/sanitize-html/CHANGELOG.md)
- [Commits](https://github.com/apostrophecms/apostrophe/commits/sanitize-html@2.18.1/packages/sanitize-html)

---
updated-dependencies:
- dependency-name: sanitize-html
  dependency-version: 2.18.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(deps): limit the lockfile to the sanitize-html bump

The regenerated yarn.lock also moved @angular-devkit/*, @typescript-eslint/*, browserslist, caniuse-lite, node-abi and others, and flipped form-data@~4.0.4 (required by @cypress/request) onto the 2.5.6 entry because the **/request/form-data resolution glob also matches @cypress/request. Keep develop's lockfile and change only sanitize-html 2.18.0 -> 2.18.1 (which drops its deepmerge dependency).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Ever Works AI <dev@ever.co>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(projects): add a comments panel for the project page

ga-project-comments reads and posts a project's comments through the
generic comment API (entity OrganizationProject), grouped into threads:
each top-level comment with its replies, one level deep. Laid out as the
objective dialog's Comments: chat-style messages with a thread card hung
off the avatar, Reply and an inline-confirmed delete where the server
allows it, a retry when the read fails, and the comment box as the footer.

* feat(i18n): add the project page's comments strings

PROJECT_VIEW.TABS.COMMENTS (the tab) and PROJECT_VIEW.COMMENTS,
COMMENTS_CAPTION, NO_COMMENTS and COMMENT_PLACEHOLDER (the panel), in
every locale; ach carries the English text. The thread strings (Reply,
delete prompt, load error) reuse GOALS_PAGE.COMMENT_THREAD.

* feat(projects): register the project comments panel

ProjectCommentsComponent is standalone and used only by the project page,
so it is imported beside the Board and Activity tabs.

* feat(projects): add a Comments tab to the project page

The tab follows the URL like the others (?tab=comments). Its pane loads
when first opened and then stays mounted, so a draft comment survives a
look at another tab.

* feat(projects): show the project's comments in the Comments tab

The pane holds ga-project-comments for the project and the selected
organization, hidden rather than removed while another tab is open.

* style(projects): give the comment reply box the comment box's height

The reply box was 2rem, barely one line; it is now 4rem like the comment
box under the list, with room for a few lines before it scrolls.

* feat(ui-core): load the user who posted each comment

CommentService.getAll() also reads createdByUser, which the server sets on
every comment. A comment posted by a user without an employee record (an
admin, say) has no employee, so this is the only name it carries.

* feat(projects): name a comment's author by the user who posted it

A comment without an employee showed "Unknown"; it now takes the name and
photo of the user who posted it. The name and avatar link to the employee
page only when there is one.

* feat(projects): link a comment's author to their profile

An author without an employee record now links too: the signed-in user to
their own profile, another user to their user profile for those holding
ORG_USERS_EDIT (the route sends everyone else to the dashboard).

* feat(ui-core): upload an image through ImageAssetService

upload() posts a file to /image-assets/upload/:folder as form data, with
the tenant and organization, and answers the recorded asset with its
fullUrl and thumbUrl. The server takes raster images only.

* feat(i18n): add the project comment image strings

PROJECT_VIEW.COMMENT_IMAGE, ATTACH_IMAGES, REMOVE_IMAGE, OPEN_IMAGE and
the COMMENT_IMAGE_TYPES / COMMENT_IMAGE_LIMIT / COMMENT_IMAGE_FAILED
messages, in every locale; ach carries the English text.

* feat(projects): carry a comment's images in its text

A comment's images travel as Markdown image lines after its text, so the
comment API needs nothing new and other clients still show a readable
link. parseCommentImages() splits them back out, taking only absolute
http(s) URLs; withCommentImages() writes them, encoding spaces and `)`.

* feat(projects): attach images to project comments and replies

Images picked, pasted or dropped on the comment or reply box show at
once from the file and upload in the background (PNG, JPEG, GIF, WebP,
BMP; up to 10 per comment). Posting waits for the uploads and adds the
images to the comment; a comment can be images only. Attaching is
offered only with ALL_ORG_EDIT or MEDIA_GALLERY_ADD, which the upload
requires. An attached image opens full size; Esc closes it.

* feat(projects): show comment images, the attach button and the viewer

A comment's images sit under its text as thumbnails that open full size
over the page, with a link to the original. Both boxes get a paperclip
and take pasted or dropped images, listed above the box with a remove
button and a spinner while uploading. A thread card reads "Image" for a
comment without text.

* style(projects): style comment images and the image viewer

Thumbnails under a comment's text (smaller in replies), the picked
images above a box with their remove button and dimmed while uploading,
the paperclip beside the box's button, and the full-size viewer on a
dark backdrop with its close button and original link.

* feat(ui-core): count a record's comments

CommentService.count() reads one row of a record's comments for the
total, replies included, so a count can show without loading the list.

* feat(projects): show the comment count on the Comments tab

The Comments panel loses its header (title, count and caption) and the
count moves to the tab button. The page reads it with the project, so it
shows before the tab is opened; the tab then reports it each time its
list is read, so a post or delete updates it. No count shows at zero or
when the read fails.

* chore(i18n): drop the unused project comments caption

PROJECT_VIEW.COMMENTS_CAPTION went with the Comments panel's header.

* feat(projects): put each comment box and its buttons in one block

The comment and reply boxes become a single composer: the picked images,
the text area, then a toolbar with the paperclip on the left and an
icon-only send button on the right (its label as tooltip and aria
label). The text area starts on one line; the "Comments" label over the
box goes, the tab already names it. NbInputModule is no longer used.

* style(projects): style the comment composer as a quiet compact block

One faint hairline around the block and nothing more: the text area drops
the global textarea styles (inset shadow, rounded corners, fixed 102px
height, input background) and grows with its text, and focus changes no
border. The paperclip and send icons are small and muted; send takes the
primary colour only once there is something to send.

* feat(projects): show the project's comments in the Activity tab

Comments write no activity log, so the Activity tab reads the project's
newest comments (CommentService.getLatest(), newest first, up to the
feed's 60) beside its logs: "Ann commented on Project X" or "replied to
a comment on", with the text on one muted line ("Image" for images
only). The project name opens the Comments tab; the author is the
employee, or the user who posted it. A failed read marks the feed
partial. ACTIVITY_COMMENTED and ACTIVITY_REPLIED in every locale; ach
carries the English text.

* fix(projects): address review of the project comments

- Images: a comment refers to its uploaded image asset (`asset:<id>`)
  instead of saving its URL. Only such references count as images, so
  comment text can no longer make the page load an arbitrary address,
  and each read looks up a fresh URL (signed URLs on S3 expire after an
  hour); lookups are reused for 30 minutes. Adds
  ImageAssetService.getById().
- Uploads that are never posted are deleted: a removed image, one
  removed while it uploaded, and an unsent box's images when its thread
  closes, the project changes or the page is left. Images being posted
  are never deleted and cannot be removed meanwhile.
- The tab's first comment count no longer overwrites a newer count the
  Comments tab reported.
- The image viewer traps the focus and gives it back to the thumbnail
  however it closes.
- Template-only members are protected, countChange is readonly, and
  collections filled after creation use let.
- Prettier formatting of the Activity component.

* fix(projects): refresh comment image addresses and gate attaching on delete

- Opening an image looks its address up again when it is too old to
  still be signed or its last lookup failed, even if the list has not
  been read again. Failed lookups are retried on the next read, and
  their thumbnail can be clicked to try again; lookups of one image
  under way at once are shared.
- Attaching is offered only to those who can also delete what they
  upload (ALL_ORG_EDIT, or MEDIA_GALLERY_ADD with MEDIA_GALLERY_DELETE),
  so an upload that is never posted can always be cleaned up.

* fix(projects): require image read access to attach comment images

Attaching also asks for ALL_ORG_VIEW or MEDIA_GALLERY_VIEW, which
GET /image-assets/:id needs: a role that could upload and delete but not
read would otherwise see its own attachments as unavailable once the
comments are read again.

---------

Co-authored-by: Ruslan Konviser <evereq@gmail.com>
Release: develop → stage (Node 26.12.1, 10 contributor fixes, project comments/activity, deps)
@coderabbitai

coderabbitai Bot commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: db580d5c-824c-4ca4-a5d4-c938d740414d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cla-assistant

cla-assistant Bot commented Oct 23, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
5 out of 7 committers have signed the CLA.

✅ aditya-mitra
✅ Cedric921
✅ joel-kalema
✅ NdekoCode
✅ evereq
❌ Senior Dev EVERDESK1
❌ dependabot[bot]


Senior Dev EVERDESK1 seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatednpm/​@​nestjs/​schematics@​11.1.0 ⏵ 12.0.799 +110084 +195 +20
Updatednpm/​@​ever-co/​legal@​0.1.2 ⏵ 0.4.182 +110077 +192 +20
Updatednpm/​@​types/​socket.io@​2.1.13 ⏵ 3.0.210010039 -3650 -25100
Updatednpm/​@​opentelemetry/​sdk-trace-base@​2.2.0 ⏵ 2.12.010010010050 -45100
Updatednpm/​conventional-changelog-cli@​2.2.2 ⏵ 5.0.0100 +1100100 +150100
Updatednpm/​@​opentelemetry/​sdk-trace-node@​1.30.1 ⏵ 2.12.0100 +110010050 -46100
Updatednpm/​atlassian-connect-express@​8.7.0 ⏵ 11.5.382 +210010050 -30100
Updatednpm/​graphql-tools@​9.0.26 ⏵ 9.0.381001005499100
Updatednpm/​husky@​6.0.0 ⏵ 9.1.7100 +110061 -1780100
Updatednpm/​jest-environment-jsdom@​30.0.0 ⏵ 30.5.21001006293 +2100
Updatednpm/​@​tiptap/​extension-task-item@​3.29.2 ⏵ 3.31.410010065 -3100100
Updatednpm/​@​tiptap/​extension-task-list@​3.29.2 ⏵ 3.31.410010065 -3100100
Updatednpm/​@​jitsu/​js@​1.10.4 ⏵ 1.11.066 -410078 +198100
Updatednpm/​nx@​22.5.2 ⏵ 23.3.067 +11100 +3193100100
Updatednpm/​jest-environment-node@​29.7.0 ⏵ 30.5.210010067 +291100
Updatednpm/​electron-builder@​26.0.3 ⏵ 26.15.399 +11006998 +5100
Updatednpm/​@​angular/​platform-browser-dynamic@​21.0.7 ⏵ 22.2.21001006997 +47100
Updatednpm/​@​types/​sanitize-html@​2.16.1 ⏵ 2.16.21001006987 +7100
Updatednpm/​jest@​30.0.0 ⏵ 30.5.2100 +110069 +191 -1100
Updatednpm/​@​ngx-translate/​http-loader@​17.0.0 ⏵ 18.0.010010070 +394 +10100
Updatednpm/​@​angular-eslint/​template-parser@​21.2.0 ⏵ 22.6.010010070 +196 +2100
Updatednpm/​@​tiptap/​extension-node-range@​3.29.2 ⏵ 3.31.4100 +110070 +1100100
Updatednpm/​get-windows@​9.2.3 ⏵ 9.3.07010010082 +1100
Updatednpm/​lighthouse@​6.5.0 ⏵ 13.5.09710010098 +170 -30
Updatednpm/​locutus@​3.0.25 ⏵ 3.0.36100 +210010091 -570
Updatednpm/​node-notifier@​8.0.2 ⏵ 11.0.099 +11001008070
Updatednpm/​date-holidays@​3.26.8 ⏵ 3.37.0100 +110010095 +170 -10
Updatednpm/​uiohook-napi@​1.5.4 ⏵ 1.5.583100938170
Updatednpm/​pdfjs-dist@​4.10.38 ⏵ 6.4.29995 -210082 +19470 -10
Updatednpm/​@​ever-gauzy/​plugin-integration-plane-api@​0.1.6 ⏵ 0.1.1177 +71007697 +170
Updatednpm/​@​ever-gauzy/​plugin-integration-plane-models@​0.1.3 ⏵ 0.1.1181 +71007397 +270
Updatednpm/​@​ai-sdk/​xai@​4.0.18 ⏵ 5.0.2171 +110087 +198100
See 379 more rows in the dashboard

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Priority Alert  (click "▶" to expand/collapse) Action
Medium priority
Obfuscated code: npm pdfjs-dist is 78.0% likely obfuscated

Confidence: 0.78

Location: Package overview

From: packages/plugins/docs-ui/package.json → npm/pdfjs-dist@6.4.299

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/pdfjs-dist@6.4.299. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn

View full report

@greptile-apps

greptile-apps Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Critical impact] The diff reaches many platform workflows and includes commerce paths tied to payments and payouts.

Fix the timer submission gap before merging so offline sync cannot upload an old duration.

Findings

  1. P1 Timer uploads miss pending changes ▶

Summary

Promotes stage into stage-apps for six Electron prereleases.

  • Ever Gauzy adds connected commerce features for selling and fulfilling products.
  • Incoming email can be sent through a Cloudflare Worker to Gauzy's Inbox.
  • Stage app builds now use Node 26.12.1.

Diagram

sequenceDiagram
    participant Timer
    participant Store as Local timer store
    participant Queue as Persistent queue
    participant Sync as Offline sync
    Timer->>Store: Mark timer unsynced
    Note over Timer,Queue: Duration job not yet enqueued
    Sync->>Queue: Wait for stored jobs
    Queue-->>Sync: No pending jobs
    Sync->>Store: Read old duration for upload
    Timer->>Queue: Enqueue new duration
    Queue->>Store: Update duration without marking unsynced
Loading

Reviews (1) · Last reviewed commit: "Merge pull request #11332 from ever-co/d..." · Reviewed by Greptile

Comment on lines +1001 to +1002
const queue = await this.asyncTimerSync(knex);
return queue ? queue.settle(timeoutMs).catch(() => false) : true;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Timer uploads miss pending changes

settleTimerJobs waits only for stored jobs, not for jobs still waiting in _submissions. With asyncTimerDataSync enabled, an offline duration update awaits markTimerUnsynced before reaching queue.enqueue. Offline sync can pass this check during that gap and upload the timer's old duration. The delayed update uses markUnsynced: false, so it does not make the timer eligible for another upload.

Wait for submissions made before this call to reach the store before waiting for the stored jobs.

@evereq

evereq commented Oct 23, 2026

Copy link
Copy Markdown
Member Author

Release Stage tagged the stage tip 46563b14d35 as v111.131.1; merging as a merge commit so the app workflows' gate finds that tag on HEAD^2.


Generated by Claude Code

@evereq
evereq merged commit 4747ee4 into stage-apps Oct 23, 2026
23 of 37 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.

5 participants