Repository navigation
Apps: promote stage -> stage-apps (stage prereleases, Node 26.12.1) - #11335
Conversation
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)
* 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)
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
|
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. |
|
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.
|
[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
SummaryPromotes
|
| const queue = await this.asyncTimerSync(knex); | ||
| return queue ? queue.settle(timeoutMs).catch(() => false) : true; |
There was a problem hiding this comment.
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.
|
Generated by Claude Code |
PR
Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.
Promotes
stage→stage-appsto 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.ymlapp workflow'scheck-release-taggate resolves the release tag onHEAD^2(the mergedstagetip, tagged byRelease Stage), as on the earlier promotions (#10257, #10469).stage-appscarries no code of its own (only promotion merges), so the content is exactlystage.🤖 Generated with Claude Code
Summary by cubic
Promotes the
stagebranch intostage-appsso 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.ymlapp workflow'scheck-release-taggate resolve the release tag onHEAD^2(the mergedstagetip), as earlier promotions did.stage-appscarries no code of its own, so the content is exactlystage.Written for commit 46563b1. Summary will update on new commits.