Repository navigation
fix(charts): limit ideal burndown tooltip precision - #9968
Open
HaiYangBG1 wants to merge 57 commits into
Open
HaiYangBG1 wants to merge 57 commits into
HaiYangBG1 wants to merge 57 commits into
Conversation
…ifications
issue-updates.html rendered each new-comment value with {{ actor_comment|safe }},
which disables Django's autoescaping entirely. Since actor_comment is sourced
from comment.comment_html - raw, unsanitized user input - a comment containing
a payload like <img src=x onerror="..."> was written straight into the
notification email and would execute in any email client that renders inline
HTML/JS, as demonstrated with a live PoC in makeplane#9218 (CWE-79, reported 2026-06-05,
still open).
Neither this path nor the sibling "mention" path sanitizes comment content
before this point - process_html_content()/process_mention() in
email_notification_task.py only resolves @mention placeholders into plain
text, it isn't a sanitizer, and comments (as opposed to mentions) don't even
go through that step.
Switch to the built-in `striptags` filter, one of the fix options already
suggested in makeplane#9218. This strips all HTML (including any tag's attributes, so
an onerror handler never reaches the output at all) and leaves the residual
text through Django's normal autoescaping, which closes the hole without
adding a new dependency. Verified against Django directly: the PoC payload
from makeplane#9218 renders as executable HTML with |safe and as an empty string with
|striptags.
Comments will render as plain text in this email rather than keeping bold/
italic/link formatting - a deliberate, conservative tradeoff. A proper
allowlist sanitizer (e.g. bleach) could preserve that formatting safely, but
picking the right allowed-tags policy needs to match what the rich text
editor actually emits, which is a decision for the maintainers; a hand-rolled
allowlist isn't something to guess at in a security fix.
…up endpoints (makeplane#9335) * fix: add rate limiting to email/password sign-in and sign-up endpoints All four password authentication views (app sign-in, app sign-up, space sign-in, space sign-up) extended django.views.View, so DRF's global AnonRateThrottle never ran and the endpoints accepted unlimited credential guesses with no friction (brute-force / credential stuffing, GHSA-349j). Add authentication_throttle_allows(request) at the top of each post() method — before any DB access — using the same AuthenticationThrottle already guarding the magic-code views. On rejection the view redirects with RATE_LIMIT_EXCEEDED, consistent with all other throttled auth endpoints. Default limit remains 10/minute, overridable via AUTHENTICATION_RATE_LIMIT. Co-authored-by: Plane AI <noreply@plane.so> * refactor: consolidate auth throttle into a decorator + add tests - Extract the repeated throttle-and-redirect block from the six redirect-flow auth views (email + magic, app + space) into a single throttle_auth_redirect decorator in rate_limit.py. Behaviour is unchanged: the throttle still runs before any DB access; brute-force traffic is rejected without a DB hit. - Add regression tests for the password sign-in/sign-up throttle on both app and space endpoints, mirroring the existing magic-code throttle tests. - Reset the shared AuthenticationThrottle bucket before every test in test_authentication.py. All auth endpoints share one per-IP throttle scope, so the newly-throttled password requests exhausted the budget mid-file and caused unrelated tests to trip RATE_LIMIT_EXCEEDED. --------- Co-authored-by: Plane AI <noreply@plane.so> Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com> (cherry picked from commit e30605d)
…ane#9382) * [WEB-8103] fix: stop leaking webhook HMAC secret_key on reads (GHSA-83rj) WebhookEndpoint list/retrieve/patch pass a fields= allowlist that excludes secret_key, but DynamicBaseSerializer.__init__ discards the caller allowlist (fields = self.expand). With WebhookSerializer using fields="__all__" and secret_key only in read_only_fields (read-only is still serialized), the HMAC signing secret leaked on every webhook read (GHSA-83rj-4282-x39v; admin-only). Rather than secret_key = CharField(write_only=True) — which would let a client inject their own secret on create/patch and break the intended one-time reveal — hide it by default and reveal only where intended: - WebhookSerializer.to_representation drops secret_key unless the show_secret_key context flag is set (secure by default). secret_key stays server-generated (default=generate_token) and non-writable. - POST create and WebhookSecretRegenerateEndpoint pass show_secret_key so the secret is still returned once for the caller to configure their receiver; list/retrieve/patch no longer emit it. Add contract regression tests (fail-before verified). Follow-up: the DynamicBaseSerializer.__init__ allowlist bug affects other serializers — tracked separately. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8103] test: address review — patch network boundary + pin to_representation Per review (@sriramveeraghanta): - Patch the network boundary (validate_url) instead of the whole private _validate_webhook_url method, so the domain/schema checks still run and the test survives a rename of the private method. - Add an assertion that an explicit fields=("secret_key",) request still hides the key, pinning to_representation as the enforcement point so a future DynamicBaseSerializer._filter_fields fix can't silently re-open the leak. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8103] docs: spell out both levels of the dead fields= allowlist The previous comment named only DynamicBaseSerializer.__init__ discarding the caller's fields=, which is half the root cause. _filter_fields never removes anything either: it builds `allowed` purely to attach expansion serializers for names not already on the serializer, then returns self.fields unfiltered (serializers/base.py:45-119). So the fields= kwargs in views/webhook/base.py are no-ops on two independent levels. Documented so a future `fields = fields or self.expand` fix isn't assumed to re-activate the allowlists for confidentiality — _filter_fields has to be made restrictive first. The show_secret_key context flag remains the sole enforcement point. Addresses @sriramveeraghanta's review on makeplane#9382. 6/6 contract tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore(security): drop advisory identifiers from code comments Explanations kept unchanged; only the IDs are removed. Co-authored-by: Plane AI <noreply@plane.so> --------- Co-authored-by: Plane AI <noreply@plane.so> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 3052792)
…eplane#9387) * [WEB-8110] fix: sanitize page list order_by against an allowlist (GHSA-2v48) PageViewSet.get_queryset passed the raw order_by query param into .order_by(). In Django 4.2 .order_by() resolves field names at call time, so an unknown field (e.g. order_by=password) raises FieldError → 500 DoS, and a valid relation path (e.g. order_by=owned_by__password) enables ORM relational traversal (GHSA-2v48-qcjw-74ch). Add PAGE_ORDER_BY_ALLOWLIST to utils/order_queryset.py and wrap the param with the existing sanitize_order_by() before it reaches .order_by(), matching the issue/project/view/notification endpoints. Unknown or malformed values fall back to the safe -created_at default. Covers only the app project-pages residual; the 3 external-REST-API sites in the advisory are handled by PR makeplane#9348. EE Wiki counterpart: WEB-8111. Add contract regression tests (fail-before verified). Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8110] fix: fold sanitized order_by into a single order_by() call (review) Address CodeRabbit + Copilot on makeplane#9387: the sanitized .order_by(user) was immediately overridden by a later .order_by("-is_favorite", "-created_at"), so the order_by param had no effect on the result (dead code) and cost an extra query-build step. Merge them into one .order_by("-is_favorite", <sanitized>, "id") — matching the EE project-pages viewset — so favourites stay pinned first, the allowlisted user ordering actually applies as the secondary sort, and id is a stable pagination tiebreak. The no-param default is unchanged (-created_at). Add a test asserting order_by=name / -name actually reorders the results. Co-authored-by: Plane AI <noreply@plane.so> * chore(security): drop advisory identifiers from code comments Explanations kept unchanged; only the IDs are removed. Co-authored-by: Plane AI <noreply@plane.so> --------- Co-authored-by: Plane AI <noreply@plane.so> (cherry picked from commit d25b99c)
…akeplane#9442) * [WEB-8283] fix: bind Spaces board object IDs to the anchor's project The public Spaces board endpoints resolved the DeployBoard from the URL anchor but trusted the caller-supplied issue_id/comment_id/intake_id verbatim, without verifying the object belonged to that board's project/workspace. Any authenticated user could write comments, reactions and votes onto arbitrary issues cross-tenant, and read EXTERNAL comments from a different project in the same workspace. Bind every caller-supplied object id to the board's project + workspace before writing: - comment / issue-reaction / vote create: require the issue to exist in the board's project via Issue.issue_objects (excludes draft/archived/ triage), else 404. - comment-reaction create: require the comment to exist in the board's project with access="EXTERNAL", else 404. - intake create: require the URL intake_id to match the board's intake, else 400. - comment list read: scope the queryset to the board's project_id. Also add the missing is_votes_enabled gate on vote create for parity with comment/reaction create (pre-existing gap in the same method). Adds contract regression tests (fail-before verified): cross-tenant writes and the cross-project comment read now rejected, with positive controls confirming legitimate board writes/reads still succeed. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8283] test: address Copilot review — cross-workspace + votes-disabled coverage - Add cross-workspace write test (issue in a different workspace) to exercise the workspace_id binding, matching the advisory's cross-tenant impact (previously only same-workspace/different-project was covered). - Add a regression test for the new is_votes_enabled gate on vote create (votes-disabled board → 400), preventing the pre-existing gap from reappearing. - Clarify the section header comment: cross-tenant writes return 404 for issue/comment binding, 400 for the intake binding mismatch. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8283] refactor: extract board-scope guards into shared helpers Address CodeRabbit review: the identical "object belongs to the board's project+workspace" existence check was duplicated across four create() methods (in four different ViewSets). Extract two module-level helpers — _issue_in_board_scope and _comment_in_board_scope — so the check is a single source of truth and cannot drift between endpoints or be forgotten on a new one (the exact class of bug this PR fixes). Behavior-preserving; 14 contract tests still green. Co-authored-by: Plane AI <noreply@plane.so> * chore(security): drop advisory identifiers from code comments Explanations kept unchanged; only the IDs are removed. Co-authored-by: Plane AI <noreply@plane.so> * fix(security): use the deploy board's project_id in the reaction-create activity log CommentReactionPublicViewSet.create() logged the activity with str(self.kwargs.get("project_id", None)) — this route's URL only ever supplies anchor and comment_id, never project_id, so every comment reaction created on a public board logged project_id="None", silently corrupting the activity/audit trail. destroy() on the same viewset already resolves the correct project_id from the deploy board; create() now does the same. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8283] fix: apply board-scope guards to comment/reaction read, update and delete paths The board/issue/external-comment scoping added by this PR's create() methods was never applied to the list, update and delete paths built on the same models. A caller could read reactions on an INTERNAL (non-public) comment through the public reaction list, or reach a comment or reaction they authored through a board it doesn't actually belong to via partial_update() or destroy(), since those methods looked up objects by pk/actor only. Bind IssueCommentPublicViewSet.partial_update()/destroy() to the board's project, workspace, issue_id and EXTERNAL access; bind IssueReactionPublicViewSet.destroy() to the board's project (previously only workspace-scoped); and bind CommentReactionPublicViewSet.get_queryset()/ destroy() to EXTERNAL comments only. Add regression coverage for each gap, plus positive controls confirming legitimate reads/writes on the board's own objects still work. Co-authored-by: Plane AI <noreply@plane.so> --------- Co-authored-by: Plane AI <noreply@plane.so> (cherry picked from commit 5b5af0a)
…akeplane#9466) * [WEB-8352] fix(security): scope SubIssuesEndpoint to the URL project (GHSA-gxhv-fw9x-2pg3) SubIssuesEndpoint is guarded only by ProjectEntityPermission, which verifies the caller belongs to the URL project_id but not that the path issue_id lives in that project. Both handlers then resolved issues without a project scope: - GET filtered sub-issues by parent_id + workspace__slug only, leaking the names/priorities/assignees/dates of another project's sub-issues (read IDOR). - POST loaded the parent by bare pk (no workspace/project scope) and filtered the moved sub-issues by workspace__slug only, letting any project member re-parent issues from other projects/workspaces (write IDOR). Scope the parent lookup and both sub-issue querysets to the URL project_id (and bind the parent to the workspace), returning 404 when the parent is not in the caller's project. Adds 5 contract tests (3 security, 2 positive controls); fail-before verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [WEB-8352] fix: dispatch sub-issue activity only for project-scoped issues (CodeRabbit/Copilot makeplane#9466) The DB update + response were scoped to the URL project, but the activity loop still iterated the raw caller-supplied sub_issue_ids. A cross-project id (excluded from the re-parent) would still fire issue_activity.delay, whose task does an unscoped Issue.objects.get and bumps updated_at — touching a foreign issue and creating a bogus activity row. Dispatch from the project-scoped sub_issues (scoped_sub_issue_ids) instead. Strengthened the test to assert the foreign issue is absent from the response body (sub_issues / state_distribution) and that no activity is dispatched for it (mock). Fail-before verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(security): drop advisory identifiers from code comments Explanations kept unchanged; only the IDs are removed. Co-authored-by: Plane AI <noreply@plane.so> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Plane AI <noreply@plane.so> (cherry picked from commit 3478d4f)
…ently dropping invalid ids (makeplane#9526) Work item create/update via the external API returned 200/201 even when assignees or labels didn't belong to the project, quietly filtering the invalid ids out with no error. Now raises the same kind of ValidationError already used for state/parent, matching the pattern of makeplane#9517. (cherry picked from commit aacfaac)
…akeplane#9200) (cherry picked from commit da1a7ab)
makeplane#9763) * fix(api): avoid re-crawling issue link on update when url is unchanged * fix(api): avoid recrawling unchanged issue links (cherry picked from commit 1fec307)
… removed Docker Hub org (makeplane#9829) MinIO removed the `minio` organisation from Docker Hub. Every deployment that pulls `minio/minio` or `minio/mc` now fails with: pull access denied for minio/minio, repository does not exist or may require 'docker login' The registry returns 401 (not 404) to anonymous clients, so the error reads like a credentials problem when the repository is simply gone. quay.io is MinIO's own second registry and still serves both images publicly (multi-arch: amd64, arm64, ppc64le). Repoint every default at quay.io, and pin an explicit RELEASE tag instead of `:latest` so a future upstream change to a floating tag cannot break deployments the same way: minio -> quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z mc -> quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z These are the digests `:latest` currently resolves to on quay. Note: the community MinIO build is effectively frozen upstream (last community push was 2025-09-07), so this unblocks deployments but does not change the longer-term need to steer self-hosted users toward external S3, with bundled MinIO as a dev/eval convenience. Claude-Session: https://claude.ai/code/session_01TB2aRDJ5BMx6m5c7jDzkqM Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit f25814c)
…akeplane#9443) * [WEB-8289] fix: scope draft-to-issue conversion to the draft owner (GHSA-vfqm-7rh7-84hq) WorkspaceDraftIssueViewSet.create_draft_to_issue resolved the draft with get_queryset().filter(pk=draft_id) — scoped only to the workspace, no created_by check — while the decorator admits any workspace ADMIN/MEMBER. Drafts are private to their creator, so any member could pass another user's draft_id to convert that draft into an issue, reassign (steal) its file assets, and delete the victim's draft. Scope the lookup to created_by=request.user (mirroring retrieve / partial_update) and 404 when no owned draft matches. The explicit null guard also removes a latent AttributeError on the following .project_id access when the draft is not found. Adds contract regression tests (fail-before verified): a non-owner member is rejected with 404 and the draft + its assets are left intact, with a positive control confirming the owner can still convert. Co-authored-by: Plane AI <noreply@plane.so> * [WEB-8289] test: make fixture ownership real (address Copilot review) Project and FileAsset fixtures passed created_by=create_user to objects.create(), but BaseModel.save() nulls created_by when there is no current request user (tests), so the ownership claim was silently a no-op. Instantiate + save(created_by_id=...) instead — same pattern already used for the draft — so the fixtures are accurate. Co-authored-by: Plane AI <noreply@plane.so> * chore(security): drop advisory identifiers from code comments Explanations kept unchanged; only the IDs are removed. Co-authored-by: Plane AI <noreply@plane.so> --------- Co-authored-by: Plane AI <noreply@plane.so> (cherry picked from commit a896115)
…invited project (makeplane#9866) `ProjectJoinEndpoint.post` looked up the accepting user's existing `ProjectMember` row by `(workspace_id, member)` and omitted `project_id`. Accepting an invitation to project Y therefore reactivated whatever membership row the user already had in the workspace -- typically a project they had been removed from -- while project Y itself never received a membership row at all. A companion no-op, `project_member.role = project_member.role`, left the stale row at its original role, so a user removed as ADMIN from another project came back as ADMIN even though the new invite was for a GUEST. Scope the lookup to `project_id` and apply `project_invite.role` on reactivation. The correct, project-scoped pattern already existed in `UserProjectInvitationsViewset.create` in the same file. Adds three regression tests covering: the invited project receives the membership, an unrelated membership is not reactivated, and a rejoin applies the invitation's role rather than the stale one. (cherry picked from commit bae037c)
…akeplane#9734) * chore: upgrade pnpm to 11.10, turbo to 2.10.11, patch postcss-selector-parser - packageManager: pnpm@11.3.0 -> pnpm@11.10.0 - turbo catalog: 2.9.18 -> 2.10.11 (latest) - override postcss-selector-parser >=7.1.0 <7.1.3 to 7.1.3 (dependabot alert makeplane#345, DoS via uncontrolled AST recursion) * chore: remove comment from postcss-selector-parser override (cherry picked from commit 3717500)
Bumps the npm_and_yarn group with 1 update in the / directory: [sanitize-html](https://github.com/apostrophecms/apostrophe/tree/HEAD/packages/sanitize-html). Updates `sanitize-html` from 2.17.5 to 2.17.7 - [Changelog](https://github.com/apostrophecms/apostrophe/blob/main/packages/sanitize-html/CHANGELOG.md) - [Commits](https://github.com/apostrophecms/apostrophe/commits/HEAD/packages/sanitize-html) --- updated-dependencies: - dependency-name: sanitize-html dependency-version: 2.17.7 dependency-type: direct:production dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> (cherry picked from commit cb28b70)
(cherry picked from commit 97ea97e, adapted: web/admin keep nginx on this branch, so only the APK security-upgrade layer is applied there)
…#9806) * fix(security): clear the 2026-09-10 nightly Trivy findings Two advisories published 2026-09-08 21:2x UTC reached Trivy's DB between the 09-09 nightly (green on every plane-* leg) and the 09-10 one, turning plane-live-community and plane-space-community red on unchanged images. - js-yaml 4.3.1 -> 4.3.2 (CVE-2026-84375). The exact pin was the patched version at the time and became the vulnerable one; the comment now says to bump rather than widen it, since a range drifts onto the 5.x break. - sharp ^0.35.3 -> ^0.35.4 (GHSA-rgj7-g3m4-5g8c, libheif GHSA-g89c-p67h-r497 and GHSA-2jg2-4ch7-h545). The third finding on plane-proxy/admin/frontend-community, CVE-2026-84445 in the Caddy binary's grpc-go, needs no change here: 2.11.4 is the newest Caddy published, and the nightly scan reads plane-ee's .trivyignore.yaml for every leg, where it is suppressed at **/bin/caddy on the reachability argument that Caddy runs no gRPC xDS server. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * chore: drop the added comment from pnpm-workspace.yaml Review feedback from @sriramveeraghanta. The diff is now two version changes and nothing else; the advisory ids and the reasoning stay in the PR description. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 2f895b8)
- vitest / @vitest/mocker: bump catalog to ^4.1.11 (path traversal / arbitrary file read via redirect mock) - qs: pin override to 6.16.0 (DoS via attacker-controlled isBuffer, array-limit bypass via bracket-key comma parsing) - morgan: pin override to 1.12.0 (log forging via unescaped Unicode line separators) - postcss-selector-parser: pin the 6.1.x line to 6.1.3 (DoS via uncontrolled AST recursion); existing override only covered 7.x - djangorestframework: 3.17.1 -> 3.17.2 (DATA_UPLOAD_MAX_MEMORY_SIZE bypass, AdminRenderer data disclosure) @tiptap/core is intentionally left on v2: the advisory is only patched in 3.30.4, a breaking major upgrade that needs a dedicated migration. (cherry picked from commit a0efb67)
* fix(security): bump the fast-uri pin to 3.1.8 fast-uri 3.1.6 is now itself affected: CVE-2026-84292 authority injection via unvalidated port serialization CVE-2026-84394 host confusion via unbalanced URI brackets, bypassing security policies Fixed in 3.1.7; 3.1.8 is the newest 3.x and carries both, so take that rather than sitting exactly on the floor. The pin is deliberately exact rather than a floor -- fast-uri reaches us only through ajv, whose range is ^3.0.1, and 4.x is outside it -- so the fix is to bump the number, never to widen the range. Rewrote the comment to say that explicitly: 3.1.6 was itself the fix for CVE-2026-75899/-75931/ -75975/-76172, so this will recur and the next person should bump rather than reach for a range. Flagged on the plane-ee nightly scan (run 36652808689, 2026-09-30), which scans the published community tags: plane-live and plane-space both reported it, and it also accounts for the fast-uri half of the plane-aio-community finding. The lockfile moves fast-uri 3.1.6 -> 3.1.8 and nothing else. Verified CI=true pnpm install --frozen-lockfile passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(deps): correct the fast-uri pin's maintenance note Mirrors the review feedback on the enterprise counterpart (makeplane/plane-ee#9951). Comment-only; the pin and the lockfile are unchanged. The reviewers there pointed out that "reaches us only through ajv" was false, because @todesktop/cli pins fast-uri directly. That is true of plane-ee but NOT of this repo -- checked, and there is no todesktop here at all, so ajv@8.18.0 really is the only consumer. Said so explicitly, with a pointer that plane-ee has the extra dependent, so the two notes do not look like one of them is stale. The substantive correction that does apply here: 3.1.8 was described as merely the newest 3.x carrying 3.1.7's fixes. It is its own security release -- CVE-2026-86472 / GHSA-hrr3-gc8f-f4qj, inconsistent host case normalization via percent-encoded octets, patched in 3.1.8 for the 3.x line. Presenting 3.1.7 as the safe minimum would have let a later cleanup drop back and reintroduce it, and because that one is MEDIUM the HIGH/CRITICAL-only nightly scan would not have caught the slip. The note now lays out all three steps of this pin's history and says 3.1.8 is the floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * docs(deps): trim the fast-uri note to three lines The maintenance note had grown to 16 lines for a one-line pin. Same facts, stated once: exact pin not a floor, ajv is the only consumer here, and 3.1.8 is the floor because its advisory is MEDIUM and the scan would miss a slip. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 6aa0d88)
…ped.sh script and minio image fix (makeplane#9727) * fix(airgapped): harden community restore script - Fix stray "+" before set -euo pipefail - Allow no-argument invocation under set -u (BACKUP_FOLDER="${1:-}") - Don't exit under pipefail when no compose project matches; recognize "running(N)" statuses when checking if the instance is running - Fix unquoted glob in the backup extraction loop - Rename extracted dirs (pgdata/redisdata/uploads/rabbitmq_data) to the volume paths the airgapped docker-compose expects (db/redis/minio/uploads/mq) - Update ASCII header to the current logo Co-Authored-By: WOZCODE <contact@withwoz.com> * fix(airgapped): address review comments on restore script - fail loudly when compose status query fails instead of treating it as stopped - use if ! tar guard since $? check was dead under set -e - clean stale extracted dirs before re-extraction to avoid merged restores - stage directory swaps with rollback so old data survives a failed move - remove unreachable else branch with misleading error message Co-Authored-By: WOZCODE <contact@withwoz.com> * fix(airgapped): restore old data if swap is interrupted by a signal Trap INT/TERM/HUP between the two mv operations in replaceDir so the volume path is never left missing mid-swap. Co-Authored-By: WOZCODE <contact@withwoz.com> * chore(docker): switch MinIO image to pgsty/minio RELEASE.2026-08-04 Replace quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z with docker.io/pgsty/minio:RELEASE.2026-08-04T00-00-00Z across the compose files and the test docs. Co-Authored-By: WOZCODE <contact@withwoz.com> --------- Co-authored-by: WOZCODE <contact@withwoz.com> (cherry picked from commit 03197e3)
…b API returns compact JSON (makeplane#9916) install.sh and swarm.sh grep for '"tag_name": "' with a space, but the GitHub API now returns '"tag_name":"v1.4.2"', so the lookup is empty. Allow optional whitespace after the colon. The exit 1 in checkLatestRelease runs inside $(...), so it only left the subshell and install carried on with an empty APP_RELEASE ("invalid reference format"). Exit at the call sites when the lookup fails. Co-authored-by: WOZCODE <contact@withwoz.com> (cherry picked from commit e6a0866)
* fix(api): pass project context when updating an intake work item IssueSerializer.validate() scopes assignees, labels, state, parent and estimate point to self.context["project_id"], but the API intake PATCH built the serializer without a context. Every in-project assignee or label submitted there was rejected with a 400. * chore(docker): bump the turbo pin in app Dockerfiles to 2.10.11 The catalog moved to turbo 2.10.11, but the builder stages still installed turbo@2.9.18 for `turbo prune`. * fix(aio): stop copying node:22-alpine's /usr/lib over the runner The copy replaced libraries just upgraded by apk (libcrypto, libssl, libz, libapk) with node's copies from a different Alpine release. node only links libstdc++ and libgcc_s from /usr/lib, so install those from apk instead. * test(api): restore shared test state precisely - celery_eager now restores task_eager_propagates as well as task_always_eager - auth tests clear only throttle_authentication_* keys instead of the whole shared cache; drop the duplicate module-level reset fixture (cherry picked from commit c7a5afe)
…#9962) (makeplane#9963) * chore(deps): switch MinIO image to pgsty/silo PGSTY forked/renamed MinIO as Silo; pgsty/minio stays published but frozen at its final tag. Bump the pinned image in all four compose files to docker.io/pgsty/silo:RELEASE.2026-09-16T00-00-00Z. Refs INFRA-1089 * fix(deps): point MinIO entrypoint scripts at the silo server binary The pgsty/silo image dropped the legacy `minio` binary; only `silo` (server) and `mc`/`mcli` (client) exist now. The default image entrypoint transparently translates `server ...` args to `silo server ...`, so services using the plain `command:` form are unaffected. But docker-compose-test.yml and docker-compose-local.yml override `entrypoint:` with an inline script that invoked `minio` directly, which no longer resolves and left both services unable to start. Point both scripts at `silo server` instead. Also fixes the now-stale image/tag reference in apps/api/tests/RUNNING_TESTS.md's service table. Refs INFRA-1089 (cherry picked from commit bab49bb) Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com>
…ne#10120) Advisories that red the plane-live, plane-space and plane-aio-community legs of the nightly scan: - body-parser CVE-2026-111181: exact pin 1.20.6 -> 1.20.9 - undici CVE-2026-109869: exact pin 7.30.0 -> 7.30.1 - zod CVE-2023-54404: no 3.x fix, so the catalog moves ^3.25.76 -> ^4.6.7 and an override keeps eslint-plugin-react-hooks on it too. The live document controller reads ZodError.issues (zod 4 dropped .errors). - curl/libcurl CVE-2026-92392: APK_SECURITY_PATCH 2026-10-26 busts the cached apk-upgrade layer so a rebuild installs 8.23.0-r0.
Ports makeplane/plane-ee#9883 to the community editor and live server: Tiptap 3.31.3, @tiptap/y-tiptap 3.0.9 and Hocuspocus 3.4.4, with no change to stored Yjs documents or the editor schema. - Editor: bubble menu on @tiptap/react/menus, AI menu on floating-ui instead of tippy.js, StarterKit's new bundled link/underline/list keymap/trailing node turned off, setContent on the v3 options form, CharacterCount/Placeholder from @tiptap/extensions. - Document codecs: v3's @tiptap/html only works in a browser, so the helpers are built from one factory and a new @plane/editor/server entry uses the happy-dom codec; live imports from it. - Placeholders are computed from the current view state, as in v2. v3's incremental placeholder state reads the previous editor state and only rescans touched blocks, so a comment box lost its placeholder after typing and deleting, and empty lines kept it after the caret left. - Live: Redis extension on the v3 API (with Redlock on the configured DB), title fragment built with y-tiptap instead of @hocuspocus/transformer, and shutdown drains pending saves before Redis disconnects. - Pin prosemirror-state/transform and the Tiptap menu extensions so the graph keeps one copy of each; dedupe linkifyjs. - Clear the strict pre-commit lint warnings in touched files (named Tiptap imports, renamed shadowed variables). Tests: placeholder regression tests in the editor, drain/Redis readiness/ shutdown unit tests and a two-server Redis integration test (v2-written pages, cross-server sync, title migration, HTML conversion) in live. Co-authored-by: Satyasai Vallampati <43840773+saivallampati@users.noreply.github.com>
…ect admins (makeplane#9444) * [WEB-8291] fix: gate project invitation list/retrieve/destroy to project admins * chore(security): drop advisory identifiers from code comments * [WEB-8291] fix: address review feedback
…e caller (makeplane#9474) * [WEB-8374] fix(security): scope ProjectMemberPreferenceEndpoint to the caller * [WEB-8374] test: assert preference payload and persistence, not just 200 * chore(security): drop advisory identifiers from code comments * [WEB-8374] fix: address review feedback * [WEB-8374] fix: omit personal fields from member-detail responses * [WEB-8374] fix: reject cross-member writes to all personal fields
…reator (makeplane#9483) * [WEB-8401] fix(security): scope public Space asset mutations to the creator * [WEB-8401] test: add unauthorized PATCH regression coverage (CodeRabbit makeplane#9483) * chore(security): drop advisory identifiers and shorten comments * [WEB-8401] fix: address review feedback
…ment (makeplane#9486) * [SECUR-236] fix: harden pagination bounds and auth brute-force rate limiting * [SECUR-236] fix: address CodeRabbit — composite account throttle key + rate guard * chore: add copyright header to authentication test package __init__ * [SECUR-236] chore: drop overlapping per_page + auth fixes, defer to makeplane#9429 / makeplane#9335 * [SECUR-236] fix: close remaining per_page crash and view/cycle mass-assignment gaps * [SECUR-236] fix: reject per_page=0 in get_per_page, not just negatives * [SECUR-236] fix: address review feedback
…ace-level asset route (makeplane#9657) * [INFRA-501] fix(security): enforce project membership on every workspace-level asset route * [INFRA-501] fix(security): stop the external-id dedup echo disclosing foreign asset ids * [INFRA-501] test: pin absence of the asset identifier fields on a denied dedup match * [INFRA-501] fix(security): require project scoping on asset create/duplicate * [INFRA-501] fix(security): validate the destination entity_id, not project_id, for PROJECT_COVER duplicates * [INFRA-501] fix: address review feedback
…ated_at on external API (makeplane#9704) * [INFRA-772] fix(security): stop trusting body-supplied created_by/created_at on external API * fix: tighten created_at assertions per CodeRabbit review on PR makeplane#9704 * [INFRA-772] fix: address review feedback
…ts + activity (makeplane#9467) * [WEB-8353] fix(security): enforce guest issue visibility on attachments + activity * chore(security): drop advisory identifiers from code comments * [WEB-8353] fix(security): extend guest issue visibility guard to comments + reactions * [WEB-8353] fix: address review feedback
…esting user's own exports (makeplane#9707) * [INFRA-778] fix(security): scope ExportIssuesEndpoint.get to the requesting user's own exports * [INFRA-778] use status.HTTP_200_OK instead of bare 200 in the new test * [INFRA-778] fix: address review feedback
…ed DRF mixins (makeplane#9652) * fix(security): refuse routed actions served by unauthorized DRF mixins * fix(security): share the non-authorizing permission set with the route scan * fix(security): resolve perform_create against its actual owner, not just its presence * fix(security): resolve the guard's permissions from get_permissions(), mirror partial_update on the other-surface scan * [INFRA-496] fix: address review feedback * [INFRA-496] test: drop routes preview now authorizes from the guard manifest Co-authored-by: Dheeraj Kumar Ketireddy <dheeru0198@gmail.com>
…bal view (makeplane#9653) * fix(security): require workspace membership to read a global view * fix: 404 instead of a hollow 200 when a global view does not exist * [INFRA-499] fix(security): reapply guest ownership restriction in WorkspaceViewViewSet.retrieve * [INFRA-499] fix: address review feedback Co-authored-by: Dheeraj Kumar Ketireddy <dheeru0198@gmail.com>
…o the URL project (makeplane#9706) * [INFRA-777] fix(security): scope ProjectArchiveUnarchiveAPIEndpoint to the URL project * [INFRA-777] fix: address review feedback Co-authored-by: Dheeraj Kumar Ketireddy <dheeru0198@gmail.com>
…L workspace/project (makeplane#9481) * [WEB-8400] fix(security): scope DeployBoardViewSet queryset to the URL workspace/project * [WEB-8400] test: cover same-workspace/other-project board scope * chore(security): drop advisory identifiers from code comments * [WEB-8400] fix: address review feedback Co-authored-by: Dheeraj Kumar Ketireddy <dheeru0198@gmail.com>
release: v1.4.3
…nt-email-notification fix(security): stop rendering raw comment HTML unescaped in email notifications
…reen [GIT-395]fix: keep timeline scroll position when toggling full screen
) * [WIKI-1268] chore: upgrade Tiptap and Hocuspocus to v3 Ports makeplane/plane-ee#9883 to the community editor and live server: Tiptap 3.31.3, @tiptap/y-tiptap 3.0.9 and Hocuspocus 3.4.4, with no change to stored Yjs documents or the editor schema. - Editor: bubble menu on @tiptap/react/menus, AI menu on floating-ui instead of tippy.js, StarterKit's new bundled link/underline/list keymap/trailing node turned off, setContent on the v3 options form, CharacterCount/Placeholder from @tiptap/extensions. - Document codecs: v3's @tiptap/html only works in a browser, so the helpers are built from one factory and a new @plane/editor/server entry uses the happy-dom codec; live imports from it. - Placeholders are computed from the current view state, as in v2. v3's incremental placeholder state reads the previous editor state and only rescans touched blocks, so a comment box lost its placeholder after typing and deleting, and empty lines kept it after the caret left. - Live: Redis extension on the v3 API (with Redlock on the configured DB), title fragment built with y-tiptap instead of @hocuspocus/transformer, and shutdown drains pending saves before Redis disconnects. - Pin prosemirror-state/transform and the Tiptap menu extensions so the graph keeps one copy of each; dedupe linkifyjs. - Clear the strict pre-commit lint warnings in touched files (named Tiptap imports, renamed shadowed variables). Tests: placeholder regression tests in the editor, drain/Redis readiness/ shutdown unit tests and a two-server Redis integration test (v2-written pages, cross-server sync, title migration, HTML conversion) in live. * [WIKI-1268] fix: honor collaboration forced disconnects --------- Co-authored-by: Sriram Veeraghanta <veeraghanta.sriram@gmail.com>
…ne#10144) * fix(security): build caddy on go-jose v4.1.6 CVE-2026-116587 (go-jose v4, HIGH, DoS parsing RSA JWKs) is fixed in v4.1.6. proxy pins it with --with, so the pin moves; admin and web inherit Caddy's own v4.1.5, so they get it in the same go get pass as x/crypto and grpc. * fix(security): bump happy-dom to 21.0.4 CVE-2026-115895 covers every 17.x-21.0.3, so there is no 20.x fix. Moving the catalog pin also replaces the auto-installed 20.14.6 peer under @tiptap/html, which is what plane-live and plane-space ship.
Contributor
Author
|
Could a maintainer approve CI for current head The PR description includes 281 passing block tests, type/lint/format checks and a before/after browser check of the tooltip. Hosted CI results are still pending. Thank you. |
…plane#10122) (makeplane#10147) * fix(web): allow saving profile settings with an empty last name (makeplane#10122) In Profile settings -> General, the last name field is optional, but form submission failed with 'Name is required' when left empty because validatePersonName rejected empty or whitespace-only inputs. - Update validatePersonName in @plane/utils to accept an optional required parameter (defaulting to true) - Update GeneralProfileSettingsForm to validate last_name with required=false so empty values pass while non-empty values enforce length and pattern constraints Fixes makeplane#10122 * refactor(utils): extract validateOptionalPersonName to preserve validatePersonName signature
…eplane#10038) * refactor(web): use public cycle sidebar close control * refactor(web): use public project state fields * refactor(web): use public Quicklinks form fields * refactor(web): align module sidebar actions * refactor(web): use public onboarding name field * refactor(web): compose public auth email field * refactor(web): use public calendar navigation controls * refactor(web): use semantic marketing consent control * refactor(web): name password reset visibility controls * refactor(web): use public password form actions * refactor(web): use public workspace name field * refactor(web): use public analytics completion badge * refactor(web): use public sidebar navigation rows * refactor(web): use public sidebar project rows * refactor(web): use public sidebar favorite rows * chore(web): align favorite folder row controls * chore(web): align extended sidebar navigation rows * chore(web): align project sidebar section controls * chore(web): use public workspace switcher identity * chore(web): align workspace switcher trigger and header * chore(web): align sidebar quick-action label * chore(web): align help menu trigger and footer * fix(web): translate extended sidebar pin controls * chore(web): align user-menu trigger focus * fix(web): resolve extended-sidebar accessible labels * chore(web): translate sidebar captions and region names * fix(web): keep workspace toggle focus ring visible * chore(web): use semantic project drag-preview typography * chore(web): use semantic favorite-folder preview typography * fix(web): render favorite drag previews before capture * fix(web): remove nested favorite-folder drag button * fix(web): improve sidebar control accessibility * fix(admin): make sidebar navigation accessible * fix(ui): separate reaction controls from picker triggers * refactor(web): use Propel inputs for header and filter searches * refactor(web): align onboarding selections and workspace fields * fix(web): separate page asset navigation and download controls * fix(blocks): preserve empty states when decorative chunks fail * refactor(web): use named Propel comment edit actions * refactor(web): use named Propel issue and webhook actions * fix(web): separate module info controls from navigation * refactor(editor): use accessible copy and image upload controls * refactor(web): associate profile field labels and errors * refactor(web): associate project settings fields and timezone feedback * refactor(web): associate workspace settings field labels * refactor(web): use public password labels feedback and visibility controls * refactor(web): associate account form feedback and public code actions * refactor(web): compose cycle dialog sections and associated validation * refactor(web): compose module dialog sections and associated validation * refactor(web): associate view title validation and description * refactor(web): compose project dialog body actions and field feedback * refactor(web): use single estimate radio activation and named grouping * refactor(web): use public webhook radio chrome and named grouping * refactor(web): use a labeled public limited-project count field * refactor(web): use public quick-add title fields * refactor(web): make issue group disclosures keyboard accessible * refactor(web): use public calendar month and year controls * refactor(web): use semantic Cycle detail typography * refactor(web): use semantic Module detail typography * refactor(web): reuse shared Intake property rows * refactor(web): use public export avatars and status badges * refactor(web): use semantic error screen typography * refactor(web): associate webhook URL labels and errors * fix(web): match List group announcements to visible content * refactor(admin): use semantic provider instruction typography * refactor(admin): use semantic settings section typography * refactor(admin): use published error banners * refactor(admin): use semantic setup copy and telemetry link * refactor(blocks): use semantic chart typography * refactor(editor): use semantic link preview typography * fix(web): translate favorite folder and drag captions * fix(space): use an accessible auth notice * refactor(space): align auth typography and password controls * fix(space): align public filter controls * fix(space): name applied filter remove controls * fix(space): make public grouping controls keyboard accessible * refactor(space): use semantic public board typography * refactor(space): align comment formatting controls * refactor(space): align comment avatars and edit actions * refactor(space): align public peek typography and actions * refactor(space): align vote control states * fix(blocks): contain long selected labels * fix(admin): bound settings content scrolling * fix(admin): contain long copy field values * fix(web): keep sidebar drag previews current * fix(web): separate overflow tab preference actions * fix(admin): expose semantic page headings * fix(admin): show keyboard focus on scrolling content * style(web): align shared header height * style(web): align sidebar rows and header with shared spacing Project rows no longer carry an extra indent for the drag handle; the projects section extends into the left gutter instead so rows start with the other navigation items. Section lists use 1px row gaps, the header uses medium icon buttons, and the scroll area uses auto scrollbars. * style(web): render board columns as panels Board columns sit in rounded layer panels on the page surface, with the group header, cards and quick-add inside the panel. Cards drop the resting shadow, use the subtle-1 border and wrap long titles; group headers use the body type scale. * style(web): use outlined property chips on work items List and board property chips render at the 24px outlined pill size, sub-work-item, attachment and link counts use the shared pill chrome, and an empty assignee chip shows only its icon (still named by its tooltip heading). * style(web): align list group headers and quick add row Group headers use the body type scale, wider trailing padding and medium ghost icon buttons for adding work items. The quick add form renders as an inline list row with a borderless title field and the submit hint on the right. * style(web): align table borders, type and column icons Table header and body cells use half-pixel subtle borders with separate border spacing, header labels use the body type scale, and cell text inherits the shared regular size. Column header icons now resolve, since the icon map is keyed by the property detail icon names. * style(web): use secondary button for calendar options The calendar options trigger uses the shared secondary button with an end chevron on desktop and a secondary icon button on mobile, matching the adjacent Today control. * style(web): use shared type tokens in the timeline Timeline header, sidebar and week/month/quarter scales use the caption and body type tokens in place of fixed pixel sizes, and the header row uses the same vertical padding as other layout toolbars. * style(web): use body type tokens in shared list rows List rows use the regular and medium body tokens for row and title text, tighten the actionable item gap, and accept link and title class names so compact surfaces can adjust spacing without forking the row. * style(web): align home widgets with the dashboard layout The home column narrows to 750px with a heading-scale greeting and wider gaps between widgets. Quick links render as a responsive grid of two-line cards, recents use compact 38px rows with bare 16px icons, empty states sit in bordered panels, and the stickies header uses the shared secondary button. Body and caption tokens replace fixed text sizes. * style(web): use type tokens in work item surfaces Fixed pixel text sizes are replaced with the matching Propel type tokens (caption, body and heading scales), folding an accompanying font weight into the token. Explicit and conditional weight classes still apply on top of the token. * style(web): use type tokens in cycles and modules Fixed pixel text sizes are replaced with the matching Propel type tokens (caption, body and heading scales), folding an accompanying font weight into the token. Explicit and conditional weight classes still apply on top of the token. * style(web): use type tokens in pages and stickies Fixed pixel text sizes are replaced with the matching Propel type tokens (caption, body and heading scales), folding an accompanying font weight into the token. Explicit and conditional weight classes still apply on top of the token. * style(web): use type tokens in project and workspace settings Fixed pixel text sizes are replaced with the matching Propel type tokens (caption, body and heading scales), folding an accompanying font weight into the token. Explicit and conditional weight classes still apply on top of the token. * style(web): use type tokens in navigation, onboarding and auth Fixed pixel text sizes are replaced with the matching Propel type tokens (caption, body and heading scales), folding an accompanying font weight into the token. Explicit and conditional weight classes still apply on top of the token. * style(web): use type tokens in shared web components Fixed pixel text sizes are replaced with the matching Propel type tokens (caption, body and heading scales), folding an accompanying font weight into the token. Explicit and conditional weight classes still apply on top of the token. * style(web): align pages list rows and header controls Give list rows a 24px icon slot and control-sized access/info icons, swap the hand-rolled search for Propel ExpandableSearch, restyle the applied-filter groups, and use filled stars and plain buttons for the favorite and lock controls. * style(web): align page editor toolbar chrome Border the sticky toolbar instead of insetting it to the content width, size the toolbar row to the header height, mark active colors with a ring instead of a trigger swatch, keep the outline overlay from blocking clicks, and position the floating pane button under any banner. * style(web): align page navigation pane and version view Scroll the assets list in a ScrollArea with full-height thumbnails and a compact download link, tighten info panel spacing and labels, use the arrow close icon, and show version errors and the view-only state with the shared empty state and Badge. * style(web): align export page modal form rows Use medium primary labels, keep the select chevron visible, and name each trigger after its row. * style(web): align stickies modal and sticky toolbar Pin the all-stickies header above a ScrollArea in a fixed-height dialog, use Propel buttons and ExpandableSearch for its actions, and rebuild the sticky formatting toolbar on Propel Toolbar with the color palette in a popover. * style(web): align comment editor toolbars Build the comment toolbar from Propel Toolbar groups and separators, keep editor focus on toolbar presses, and use labelled icon buttons in the lite toolbar. * style(web): align top navigation and user menu layout Group the header into start, search and action regions with responsive gaps, use 18px header icons, drive open-trigger states from the popup data attribute, and rebuild the user menu card with a darkened cover, truncated name and email, and a separated sign-out row. * style(web): restyle customize navigation dialog and project header Use section titles, layered list boxes and Field-based checkbox rows in the customize navigation dialog, switch the project mode options to radio field rows with a separator, use the primary text color for the project header name, and render the project actions trigger as an icon button. * style(web): restyle notifications sidebar Replace the breadcrumb header with a titled header carrying the mark all read and refresh actions, move filters and options next to the tabs, render the list in a scroll area with a tab panel, emphasize unread rows by text weight instead of a tinted background, and use ghost icon buttons for row actions. * style(web): align workspace switcher and favorites styling Lay out workspace switcher rows as padded rounded cards with the tick inside the row, scroll the workspace list in a scroll area, drop the account email header, separate sign-out from the other actions, and tune favorite icon sizes, star color and the new folder input. * style(web): truncate Power K rows and refine shortcut badges Keep command row labels on one truncating line with a tooltip for search results, render work item identifiers outside the truncated name, draw Mac modifier keys as icons, wrap shortcut rows, and use workspace avatars in the workspace menu. * style(web): tighten progress stat rows in cycle and module sidebars Progress stat rows use body-xs text, a flexible title column with a tabular count column, 2xs avatars and compact empty states. Clickable rows are now reachable and operable from the keyboard. * style(web): align module cards and list rows Module cards put the info, favorite and quick-action controls in a fixed-height title row and move dates and status into a wrapping pill row. Module dates and status render as small pills with a status icon, list rows use the success ring and small ghost quick actions, and the empty lead slot uses a 20px icon box. * style(web): align cycle list rows Cycle rows lead with a 16px cycle status icon, open details from a compact info button that also appears on keyboard focus, and render transfer and date range as small pills. Quick actions use the small ghost trigger, and active cycle stat chips use layer hover tokens. * style(web): restructure cycle and module detail sidebars Detail sidebars use a larger title, a status badge or icon beside it, a Properties section built from shared property rows, and divided sections with caption section headers. Module status, dates, lead and members move into the property list, links get a header count and an add button, and the peek panels drop the unused transition. * style(web): restyle module link rows Link rows show the link icon in a 32px tile, the title over a muted meta line, and reveal labelled edit, copy and delete icon buttons on hover or focus. The link modal uses large fields with wider spacing. * style(web): compact cycle and module forms, filters and transfer modal Create and edit forms use small property pills without width caps, applied filter groups use the compact layered container with only the group label capitalised, and the transfer modal drops the title icon and uses compact option rows. * style(web): refresh work item link, attachment and property rows Round link cards to rounded-lg with a secondary link icon and a ghost copy button, restyle attachment cards as single-border rows with a truncated name, muted meta line and right-aligned actions, and give the parent, cycle, module and label rows a consistent 32px height. * style(web): restructure work item detail and peek layout Show the identifier (with the parent as a breadcrumb) above a larger title, move state, priority, assignees and dates into an inline property bar under it, and clip long descriptions behind a Show all / Show less toggle that expands on focus. The sidebar and peek properties now share one component that groups the remaining properties into collapsible Details and Project structure sections with a created/updated footer. The detail page uses a flex-1 main column with px-8 py-6 and a px-6 py-6 sidebar; the peek view drops the stacked spacing and lets the full-screen properties panel scroll. * style(web): show work item activity as All / Activity / Comments tabs Replace the activity heading with an underlined tab row (All, Activity, Comments) that keeps the sort and filter actions on the right. Each tab covers a fixed set of the existing activity filters, the filter menu now refines within the active tab, and the comment box is hidden on the Activity tab. The tabs support arrow-key navigation and the last tab is remembered locally. * style(web): tighten work item peek header and widget spacing Space the peek header controls 8px apart, use an expand icon for the full-screen link and give the layout switcher a 16px icon with a hover background. Separate the widget action row from the widget sections with 24px, shrink the section add icons to 14px and use a small dot for the sub-issue filter indicator. * style(web): narrow settings content column and frame members loader Use a 50rem content column with an inset scrollbar lane, and draw the members loader as a framed table skeleton matching the members table. * style(web): align profile general form and overview cards Overlay avatar, name and cover picker on the profile cover, switch the fields to InputField in a two-column grid, and give the profile overview cards a flat bordered layer surface. * style(web): align workspace details and members settings Use WorkspaceAvatar, TextButton and InputField in workspace details, render member avatars with Avatar, dim suspended rows, frame the empty members state, and label the delete-workspace inputs. * style(web): align estimate settings and point rows Render estimate points as layered cards with an actions menu, use InputField and labelled Cancel/Add buttons in point forms, and match empty-state and option card surfaces. * style(web): align project state groups and state form Use layered rounded group and state rows, a state-group colour swatch, a footer action bar in the state form, and a grouped skeleton loader. * style(web): align project label rows and inline label form Use bordered label rows with inline actions, label icons with truncating names, and an InputField-based inline form with an icon colour trigger. * style(web): align exports, webhook form and API token list Drop the exports header rule, frame empty states, use InputField for the webhook URL with flex section spacing, and render API tokens in a framed list with status badges and a keyboard-reachable delete button. * style(web): align project general and members settings Use InputField and TextAreaField in the project form, box the member defaults, and match the members header, avatar column and empty state. * style(web): align onboarding steps and tour heading Use selectable card styling for role, use-case and invitation choices, avatar and ghost upload button in the profile step, a compact invite row layout in the team step, and loading buttons in place of spinners. * style(web): refine list, table, calendar and filter details Row and header selection checkboxes share the 16px slot at the row edge, calendar blocks sit on layer-2 pills with an icon-button menu, quick-add forms gain consistent padding, board header actions use ghost icon buttons and the display-filter header matches its popover surface. * style(web): render board swimlanes as continuous column panels Column headers cap rounded layer panels that run through every sub-group band at the board's 336px column width, sub-group headers float as bordered strips across them, and the last band (or a footer cap when it is collapsed) rounds the panels off. * style(web): use hairline borders in timeline grid and base layouts Timeline month, week and quarter grids draw right borders instead of overlapping outlines and the sidebar header checkbox matches the list slot. Base layouts get the raised selected segment in the layout switcher and board group headers with primary titles and medium counts. * style(web): align views, analytics and filter row details View rows show the published state as a success badge with translated access tooltips, applied view filters group their values, analytics uses page gutters, h6 section titles and a stable insight-card height, and the rich filter row drops its doubled padding. * style(web): align intake list and status chips Intake statuses render as propel badges, list items use the layer hover and selected surfaces, the open-tab count uses the tab's own styling and empty sidebars use compact empty states. * style(web): align shared pickers, modals and loaders The existing-work-items modal uses edge-to-edge search, selection, list and footer sections, the cover image picker fills its popover with stretched tabs and inline upload actions, archive tabs use underline tabs, count chips use brand badges and the projects and table loaders follow the current card and column geometry. * style(web): fix work item title and detail sidebar widths Read-only work item titles use the same heading size as the editable title on the detail page and peek. The work item sidebar uses a fixed 320px column on desktop so the inline property bar has room, and the module detail sidebar uses the same 16px gutter as its peek panel. * fix(utils): keep Propel border and outline widths when merging classes tailwind-merge read the named width steps (border-sm, border-t-xs, outline-md, ...) as border colours, so a following colour class such as border-subtle-1 dropped the width and outline pills rendered without a border. The steps now extend tailwind-merge's own width groups, and the 2xs/3xs caption and 2xs body tokens are recognised as typography. * style(blocks): let pill select triggers use the shared pill width cap The pill and icon-pill triggers no longer clamp to 160px, so long values truncate at the pill's own maximum width, and the select test asserts the outline pill keeps its border width. * style(web): align work item ids, board card actions and table cell hovers Work item identifiers in layouts use the 12px medium caption at every size. Board card quick actions stay in the layout while hidden so the title no longer reflows on hover, table count and date cells use the transparent layer hover, and the sub-work-item column's selected hover no longer fills with the full accent colour. * style(web): size label and draft chips and colour due dates by urgency Label and draft property chips use the 24px pill like their siblings, sub-work-item rows show the estimate chip, the sub-work-item count uses its own icon, and the attachment and link counts are real buttons that open the work item. Due dates turn crimson once they have passed and yellow on the day itself, through a shared getIssueDueDateClassName helper. * style(web): highlight the filters toggle while open or filtered The rich filters toggle uses the accent border, subtle accent fill and accent text while the filter row is open or any filter is applied, instead of switching to the tertiary button only when filters exist. * style(web): size form fields and pin the project create actions Cycle, module and page title fields use the xl InputField and the cycle, module and view descriptions the xl TextAreaField; project create and view forms use the lg size. Errors render through the field so labels and messages stay associated. The project identifier info icon becomes a focusable end-of-field icon button with the same tooltip. The project create modal insets its content, keeps the cover rounded inside it, lets the fields scroll under the cover and pins the action row. * style(web): use compact search empty states for no-result lists Cycles, archived cycles and views lists show the compact search empty state when filters or search hide every item, and archived modules the detailed search state, instead of the legacy filter illustrations. The archived lists reuse the existing translated copy rather than hardcoded English, and the unreachable "No results found" branch in the views list is removed. * style(web): use underline route tabs and an always-visible table search in analytics Analytics tabs are underline links to their routes, filling the page under a header aligned to the page gutter. The insight table always shows a large search field next to its row count, exports through a medium secondary button with the export icon, scrolls inside a bounded frame under its sticky header, and shows the search empty state when a search matches nothing. Chart and skeleton heights come from one shared class. The no-projects action label is translated. * style(web): show the work item analytics button as an icon in narrow headers Below the header's @4xl container breakpoint the analytics action is a secondary icon button with the analytics icon and its tooltip; at and above it, a labelled secondary button. * style(web): use detailed empty states for 404, error and not-found screens Swap the legacy 404, server error, not-authorized, workspace-not-found, profile no-access and work item/cycle/module/view not-found images for the shared empty-state illustrations at size-40 with lg actions. The production error page gains a Reload action next to Go to home. * style(web): move auth forms onto xl input fields Email, password, unique code, forgot, reset and set password forms use the ready-made xl input field with labels, end-icon toggles and inline errors. The forgot password link grows to caption-md and, without SMTP, reveals an inline danger banner instead of a popover. Resend code is a plain text button, the invite header shows an xl workspace avatar and auth error banners use the info variant. * style(web): refresh invitation and create workspace screens Workspace invitation states and the empty pending-invites page use the shared empty-state illustrations with real buttons and links in place of the old list rows. Pending invites show an xl workspace avatar and a capitalised role under an h6 lead-in. The create workspace form caps at 24rem and the creation-disabled copy uses body-xs-medium. * style(web): use shared empty states on the stickies page The stickies page empty and no-results states use the stickies and compact search illustrations, and the header icon drops to size-4 in the tertiary tone without the rotation. * feat(web): describe the empty notification detail pane Show the inbox illustration with a title and description when no notification is selected. Add notification.empty_state.detail.description and issue.empty_state.no_access.{title,description} in every locale, and retitle the notification detail empty state. Translations are machine generated and need native review. * feat(blocks): add entity detail, activity timeline and comment box blocks entity-detail provides the bordered widget toolbar (a flat medium Propel Toolbar with separated groups and text, menu and icon buttons) and the collapsible widget section with title, count, caret and open-only actions. activity provides the timeline row (bordered icon box on a rail, actor, message, dot and time) and the comment card chrome with a 16px avatar column. comment-box wraps an editor with a formatting toolbar slot and a submit button. * feat(i18n): add work item detail toolbar, archived banner and updated strings Adds the accessible name for the work item widget toolbar, the archived work item banner message and the "Updated {time}" sidebar meta in every locale. * style(web): group work item widget actions in one toolbar The add sub-work item, relation, link and attach actions sit in one bordered toolbar with separated groups, keeping their menus, file picker and drop target. Sub-work items, relations, links and attachments become divided sections with a plain title, count and caret, actions only while open and no body inset. The sub-work item progress is a compact ring and x/y chip beside the title, and relation groups are headed by soft pills. * style(web): use timeline rows, comment cards and a comment box in activity Activity rows use the timeline block: a raised bordered icon box on the rail, 24px row spacing, medium actor, secondary message and a dot before the time. Comments render as raised cards with a 16px avatar column, author, "commented", visibility and time on one line. The composer sits in a bordered comment box with the formatting toolbar and a Comment button in its footer; mentions, uploads and Enter to submit are unchanged. Empty activity and comment tabs show a compact empty state. * style(web): polish work item sidebar, peek archive state and create modal The detail sidebar shows "Updated <time ago>" beside the Properties title and uses the created, updated and completed date icons for its audit rows. The subscribe button is label only. An archived work item in the peek shows a warning banner with Restore and dims its content. The create modal description box gains hover and focus states, and the selected parent is a bordered pill with a labelled remove button. * style(web): turn the display and work item filter dropdowns into menus The shared filters dropdown opens a Propel menu instead of a popover. Display shows the property toggle pills on top, then Group by, Sub-group by and Order by as submenus that name the current value, with the direction as its own choice under Order by, and the extra options as checkbox rows. Work item filter sections become one submenu per property with a search on long lists, and date filters open the custom range modal from a host that outlives the menu. * style(web): move project, cycle, module, view, page and intake filters into menus Each filter dropdown now lists one submenu per property with the applied count at the end of its row, a search inside long lists such as members and labels, boolean filters like My projects and Favorites as checkbox rows above a separator, and date filters whose Custom row opens the range modal. The module filters drop their bespoke menu wiring and use the shared filters menu. * style(web): show applied filter values as removable chips Adds a RemovableChip on the Propel combobox chip, with an outline look and a soft look for chips inside a filter group, and uses it for every applied filter value on the projects, cycles, modules, views, intake and work item rows. Each filter group now sits in the same bordered layer-2 frame with the property name, its chips and the clear control. * style(web): render work item filter conditions as pill groups A filter condition is now one Propel pill group: the property as a pill with its icon in the tertiary tint, the operator and value as 28px pill select triggers, and remove as an icon pill. An invalid condition outlines every segment in danger. The add-filter dropdown can take the property pill as its trigger so the group keeps it as a direct segment. * style(web): use the expandable search in list headers The cycles, archived cycles, modules, archived modules, views and projects headers swap their hand-built search toggle for the Propel expandable search with a clear control. The modules header now reads search, layout, an icon-only Filters button and Order by. * feat(utils): add an off-DOM measureTextWidth helper Measures text in a CSS font on a shared canvas (cached per font and text) so layouts can estimate chip widths without forcing a reflow, with an average-glyph fallback where no canvas exists (SSR, jsdom). * feat(blocks): add LabelPill and show label colours as Propel swatches LabelPill is a display-only 24px Propel pill (soft or outline) with the label colour as a Swatch and the name as a truncating PillLabel, capped at the smaller of 224px and its container. The label select lists selected labels as soft label pills in its field variants, uses swatches instead of hand-drawn dots in its trigger and options, and can hide the empty label icon with showEmptyIcon. * feat(blocks): add a single-line work item properties row with a +N menu PropertiesOverflowRow fits a work item's property chips into a width budget, most important first, and collapses the rest into a +N pill whose Properties menu lists each hidden property with its value; editable rows open the property's own editor, count rows run their action and read-only rows are disabled. Shrinkable chips (labels) step down before anything collapses, and a chip whose dropdown is open is never collapsed. * feat(i18n): add strings for the list row properties overflow Adds issue.properties_overflow.trigger_aria_label (the +N trigger's accessible name) and issue.properties_overflow.label_overflow_indicator (the +N labels pill) in every locale. * style(web): keep list row properties on one line with a +N menu From the single-line breakpoint, list rows keep their property chips on one line within the room left after a 240px title floor; chips that do not fit collapse into a +N pill opening a Properties menu where each property can still be edited. Labels step down from up to three outlined label pills, to fewer pills with a +N labels pill, to the N labels chip. Narrow screens keep the wrapping row and board cards are unchanged. * style(web): collapse workspace draft properties into a +N menu Workspace draft rows use the same single-line properties row as work item lists, including the labels step-down. * style(web): show work item sidebar labels as soft label pills The labels property in the work item sidebar, peek and intake lists the selected labels as soft label pills with colour swatches and no longer shows the label icon while empty. * style(web): label multiple assignees as a count in the property bar The member select accepts a multipleLabel for selections of more than one member, and the work item property bar uses it to show "N assignees" instead of hiding the label. Adds issue.assignee_count to every locale (machine-translated plural forms; needs native review). * feat(web): add a sticky column picker column to the spreadsheet layout The table ends in a narrow column pinned to the right edge. Its header holds a column picker that toggles the built-in display properties through the same filter store update as the Display menu, and each row's quick actions move out of the title cell into it, revealed on row hover, keyboard focus or while the menu is open. The loaders draw the extra column too. * feat(blocks): add activity, archive, comment and widget empty-state illustrations Draws new card-stack illustrations for activity timelines, archives, comments and widgets (plus a plate-stack archive for detailed empty states) using the shared illustration colour tokens, so they follow light and dark themes. The art is decorative and hidden from assistive technology. Registers the keys so EmptyStateCompact/EmptyStateDetailed resolve them, with render tests for each key. * style(web): show the activity illustration in profile activity empty states The profile overview's recent activity card and the first page of the profile activity list now show the activity illustration with the "No activity yet" message instead of the generic fallback or an empty list. Comment actors in the activity list use the shared avatar at the medium size instead of a hand-rolled image. * style(web): use block illustrations for home widgets and work item activity The home page's no-widgets state uses the compact empty state with the widget illustration instead of a theme-switched raster image, and the work item activity and comments tabs use the activity and comment illustrations instead of the update and note fallbacks. * style(admin): rebuild the sidebar on the shared list and overlay pattern Render the instance settings menu as a comfortable list inside a scroll area, showing names only with descriptions in tooltips. The sidebar is 288px wide (70px collapsed), overlays the page below md with a backdrop and Escape to close, and the layout fills the dynamic viewport. The header uses the user-cog tile and a round avatar with a danger sign-out row, and the footer shows a secondary Redirect to Plane button. * style(admin): align page header spacing, copy fields and setting switches Pages start 24px (32px on md) from the header with the title row stacking on small screens, and wide pages keep a 16px inset. Copy fields show the value in a bordered code box with a ghost copy button, toast on success or failure, and switch labels read as primary body text. Adds common.clipboard_copy_failed, common.tooltip.copy_to_clipboard and instance_workspaces.empty_state in every locale. * style(admin): align authentication cards, provider forms and email settings Authentication method cards stack on small screens with a rounded icon tile and a larger title and description, and Configure uses the preferences icon. The sign-up toggle sits in a bordered card. Provider forms use tighter column gaps and primary section titles with the Plane-provided details in a panel below their heading. Email settings label the security select as a field and the test email dialog is wider with a labelled input. * style(admin): align workspace, setup and not-found screens The workspace form shows validation as field errors with translated labels and a secondary organization size select, and the workspace list gains a bordered empty state, a stacking header and a bordered creation toggle card. The 404 page uses the shared detailed empty state. Instance setup uses xl inputs, the shared password strength meter and an inline danger banner, which sign-in also uses, so the local banner and meter copies are removed. * style(admin): use shared skeletons, lockup and type tokens Loading placeholders and the Plane lockup now come from the shared blocks package, so the local copies are removed, and the last fixed text sizes in the new user popup and instance failure screen move to body and heading type tokens. * style(space): lay the public board out as column panels on surface-1 Board sits on surface-1; kanban columns become rounded layer-1 panels with the header inside, list group headers get a layer-1 bar with a body-sm-medium title, and an empty board shows the compact work item empty state. * style(space): give board cards the raised card shell Cards use a subtle-1 border on layer-2 that lifts on hover, pad the body at 12px with 12px gaps, wrap long titles and put the reactions footer on layer-2. * style(space): show board properties as read-only outlined pills State, priority, labels, dates, assignees, modules and cycle render through the shared property selects in disabled mode as 24px outlined pills (clicks fall through to the card), counts become outlined Pill chips, due dates take the shared overdue/due-soon tone, and the board honours the default computed display properties. * style(space): align the peek properties and comment thread Peek properties sit in fixed-width label rows (16px icons, 32px rows) with read-only ghost selects and a new Labels row. Comments open with the composer, list newest first and use the shared activity comment card with the timeline rail. * style(space): replace legacy text sizes with type tokens Fixed pixel text sizes move to the Propel type scale with weights folded in, and the unused label filter components and renderEmoji helper are removed. * style(space): use the shared empty states for error and 404 pages The not-found pages and the board error state render EmptyStateDetailed with the 404 and server-error illustrations, replacing the local SVGs. * style(space): build the comment toolbar on the Propel Toolbar Formatting controls become grouped Toolbar toggles with separators and tooltips, keep editor focus on press, and the submit button sits beside the toolbar instead of inside a bordered strip. * style(space): polish the board filter menu Options use the Propel checkbox and radio, section headers read as placeholder text on a layer-2 sticky bar, the search field drops its extra side padding and the Filters trigger moves to the md size. * style(space): match the navbar layout switcher and sign-in button The layout switcher uses the raised segmented buttons with tooltips and the sign-in button moves to the md size. * feat(blocks): add SegmentedProgress and ProgressRing progress blocks SegmentedProgress draws a stacked bar on a surface-2 track with one coloured segment per item sized by its share of the total, a tooltip per segment, four sizes and optional click/keyboard activation. ProgressRing is a sized circular ring with a strong-border track, a success arc and a centred slot for a percentage or check mark. * style(web): show module and active cycle progress as state group bars Module cards and the active cycle progress panel replace the single-value linear bar with a large segmented bar (one segment per state group, with a tooltip each). Module list rows draw their completion with a 30px progress ring with a 3px stroke instead of a scaled circular progress. * feat(i18n): add module progress summary and board collapse strings module.details_drawer.progress.{done,points_done,empty} back the module sidebar progress headline and empty state; aria_labels.project_board. collapse_group names the board column collapse button. All locales. * style(web): redesign the module details sidebar The header gains the module quick actions beside close. A long description clamps to two lines with show all / show less. Properties, progress and links are collapsible groups separated by dividers. Progress leads with a bold percentage and a "N of M work items done" caption (points when plotted by points), a thin stacked state bar and a two-column state group grid with count and share; on the module page the tiles filter the work items by state group, replacing the old States tab. The burndown chart and the assignee and label breakdown stay below. * style(web): clip long cycle sidebar descriptions behind show all The cycle sidebar description renders at the xl text size inside the work item description overflow, so anything taller than 140px fades out behind a Show all / Show less toggle. * style(web): make collapsed board columns one expand button A collapsed column is now a single button over the whole strip with a layer hover, focus ring and a "name · count" tooltip that follows the cursor. It shows the 16px group icon, the vertical name capped at max-h-100, the count and a 12px expand glyph. Columns can also be collapsed in swimlanes: the header cell, every band cell and the footer cap become expand targets that highlight together on hover. The expanded header's collapse button gets a translated label. * feat(blocks): let FavoriteStar take a button size and variant Callers can now ask for a bordered secondary star or a larger box; the compact ghost button stays the default. * style(web): redesign the project card around a faded cover and footer Shorter cover fading into the card, logo tile in the corner, actions and favorite revealed on hover as secondary buttons, name and identifier below the cover, a lead row, and a footer with a Joined/Archived badge or Join button next to the member stack. Settings, restore and delete move into the card menu. The loader mirrors the new card. * style(web): build settings sidebars from list rows with section headings Workspace, project and profile settings sidebars now use comfortable list rows with medium icons under static section headings, with arrow-key movement inside each group. The workspace and project headers stay pinned above the scrolling list with a tertiary back button and a large workspace avatar. The profile settings modal gets a wider surface-1 sidebar, roomier content padding and a ghost close button. * feat(blocks): add a type-to-confirm dialog for destructive actions An alert dialog with a danger badge, corner close and one or more labelled inputs that must match before the danger confirm unlocks. Enter in any input confirms, the typed text clears each time it opens, and close requests are ignored while the action runs. * style(web): confirm project and workspace removal by typing in an alert Delete project, leave project and delete workspace move to the shared type-to-confirm alert and keep their name plus phrase inputs. Leaving a workspace now also asks for the workspace name. * style(web): give state rows a persistent actions menu and default badge Each state row now shows a larger state icon, a medium-weight title, a Default badge on the default state and an always-visible menu with edit, mark as default and delete (disabled with the reason when the state can't be removed, confirmed before deleting). The drag handle is a bordered chip. * style(web): add a sticky search and add toolbar to project labels The labels page heading drops its button; a sticky toolbar below it holds a search field and the Add label button. Searching flattens the tree into the labels whose name matches. * feat(constants): add help-center links for settings pages A map of docs.plane.so pages for the workspace and project settings screens that show a Docs link. * style(web): link settings headings to their docs and enrich boxed controls Settings headings take a docHref and show a small secondary Docs link after the description; states, labels, feature, exports and webhooks pages use it. Boxed control rows can now carry a leading icon tile, an accessory beside the title, a docs link and an attached footer panel. * style(web): present project features as a titled section of enable toggles The features list gets a smaller section heading and each row reads "Enable <feature>" with the Pro badge beside the title. * style(web): show automation options in a panel attached to each toggle Auto-archive and auto-close become boxed toggle rows with a medium switch; while enabled, their duration and state options slide open in a panel joined to the bottom of the row. * style(web): list access tokens in a searchable table Personal access tokens move from stacked cards to a table with name (and a description tooltip), status badge, created, expiry and last-used dates and a row menu for deleting. A toolbar above it filters by name or description and holds the add button once tokens exist. * style(web): give workspace members a described heading and toolbar row The members page uses the settings heading with a short description, and search (now an expandable search), role filters and Add member sit in a right-aligned toolbar row with a divider beneath it. * style(web): start workspace exports from a heading button and dialog The inline export form moves into a medium dialog opened by an Export button in the page heading, with project and format pickers on label rows and Cancel/Export actions; the page below lists previous exports. The dialog closes once an export is queued. * style(web): list webhooks in a table with a row actions menu Webhooks show as table rows with the URL (linking to the webhook), an Active/Disabled badge and the created date. The inline switch moves into a row menu with edit, enable/disable and a confirmed delete; the delete modal now also takes the webhook id directly. * style(web): use an expandable search in the project members toolbar The always-open search box beside the role filter becomes a collapsed search icon that expands into the input, like the workspace members page. * chore(web): remove assets and helpers left unused by the UI refresh Deletes empty-state, auth and filter illustrations that the shared empty states replaced, the EmptySpace component, the old FilterHeader and FilterOption popover helpers, and the page lock text and icon animations that no component uses any more. * feat(web): show a no-access state for forbidden work item links The work item identifier lookup keeps the HTTP status on its thrown payload, and the browse page shows the no-access empty state when the API answers 403 instead of the generic not-found screen. * style(web): use the archive illustration for archived projects and link the estimates docs The archived projects empty state shows the archive illustration instead of the archived work item one, and the estimates settings heading links to its docs. * fix(web): show the estimate value in the properties overflow menu A set estimate rendered an empty preview in the "+N" properties menu; it now shows the point's display value like the other rows. * style(web): keep focus rings visible on the edge chips of a list row The clipped properties row gets 2px of padding offset by a matching negative margin, so the first and last chips' focus rings are no longer cut off. * feat(blocks): add entity detail votes and voters modal EntityDetailVotes renders round up/down vote toggles, tinted while the viewer's vote is on them, each followed by a count that opens the voter list. EntityDetailVotesModal lists voters with avatars under upvote and downvote tabs, with an empty state per tab. * style(space): show votes with a voter list modal Board card footers and the peek view use the shared votes block: round vote toggles, and clicking a count opens a modal listing who voted on each side. Adds issue.vote title, tab count and empty-state strings. * style(admin): build the header on the shared breadcrumbs The admin header is a header landmark with px-4 padding, its trail uses the shared Breadcrumbs (collapsing on narrow screens with base-path aware links), and the mobile sidebar toggle is a ghost icon button that reports its expanded state. * fix(admin): enable Save only for unsaved changes General, image and email settings keep Save disabled until a field changes and reset to the saved values after a successful save. Picking an email security option now counts as a change. * style(web): show loading and retry states for the work item description The description area shows a two-line skeleton while the work item details load, and a "Couldn't load the description" line with a Retry button when they never arrived, instead of mounting an empty editor. * style(web): fall back to the link glyph when a link favicon fails Link rows keep the favicon from the link metadata, but swap back to the link glyph when the image fails to load, and the favicon is now decorative (empty alt). * style(web): enlarge activity sort and filter buttons and mark applied filters The activity header sort and filter buttons use the md icon button size, and an applied filter shows the filled filter glyph instead of the corner dot. * style(web): break down sub-work item progress in a hover popover Hovering the sub-work items progress chip opens a 26rem popover with the completion percentage, a segmented bar per state group and the five state group counts. * style(web): edit comments inside the comment box chrome Editing a comment uses the same bordered comment box as the composer: formatting toolbar and a ghost Cancel button in the footer, Save as the submit button. Mentions, uploads and Enter-to-save are unchanged. * style(web): translate the formatting toolbar and intake tooltip labels The comment formatting toolbar's accessible name and the intake list item's created-on and priority tooltips now come from translations. * style(web): stack the snooze dialog pickers with AM/PM tabs The custom snooze dialog now stacks the date and time pickers, switches AM/PM with contained tabs in the time picker header and translates its title, labels, placeholders, empty message and actions (new notification.snooze.modal.* keys and a common submitting key in every locale). * style(web): tidy the inbox display menu and load more link The inbox display options open from a preferences icon with a translated label, their checkbox rows drop the leading icons, and loading the next page is a plain accent text link instead of a ghost button. * style(web): align home recents rows and widget loaders Recent work item state and priority glyphs sit in medium icon slots with plain tooltips, assignees and project members show as a compact avatar stack of up to three (ButtonAvatars gains a max option), the recents filter pill uses medium body text with a small chevron, and the recents and quick links loaders mirror the real rows and link grid. * style(web): combine the invite email and role into one control Each onboarding invite row is a single bordered control holding a borderless email input, a divider and a ghost role select, with labelled inputs, translated column headings and an always visible remove button that is disabled for the last row. * style(web): scroll the extended projects sidebar in an inset area The expanded projects panel lists projects in an inset scroll area with even padding and tight row spacing, uses the compact search illustration when nothing matches, translates its title and casts a directional shadow. * style(web): point the help menu sales item at the sales page Contact sales uses the alternate user icon and opens the public talk to sales page. * feat(i18n): add page version history, locked badge and view control strings Adds the version pane's view-only, loading, error, retry and restore strings, a Locked label for saved views, and the Save menu / hide-filters labels for the filters row, in every locale. * style(web): size the page title area to fit the page icon The space above the page title is 64px, or 71px once a page icon is set, and the icon tile sits directly under it without the extra top margin. * style(web): give page version history its own pane with day groups Version history moves out of the Info tab into its own navigation pane view, opened from the page options menu, with a title row and close button. Saved versions are grouped by day (Today, Yesterday, then dates) under small caption labels, the current version leads today's group, and a failed list load shows a retry. The version preview header gains a top border and its strings are translated. * style(web): show work item count and status badges in view headers Saved view headers show the work item count as a small brand badge next to the view name and a neutral Locked badge with a lock icon when the view is locked. Project views also show a green Live badge linking to the published view, and the Display menu opens from an icon-only preferences button. * style(web): merge filter row save actions and add a minimise button When a view can be both updated and saved as new, the filters row shows a single Save menu holding Update view and Save as instead of two buttons; with only one available it stays a plain button. A ghost collapse button at the end of the header row hides the filters row again. * style(web): align timeline and list quick-add rows with their layouts The timeline full-screen toggle uses the expand arrows icon. The timeline quick-add row is 48px tall with 16px side padding and a large title input. The list quick-add identifier takes the same width as row identifiers and reserves the sub-work-item chevron's slot, so the title input lines up with the row titles above it. * style(web): show calendar work item ids with a truncating project key Calendar blocks render the work item id in the medium caption style, with the project key truncating ahead of the sequence number so the number stays readable in narrow day cells (the full id stays available as a title and to screen readers). The hover preview uses the rich popover style explicitly. * fix(utils): parse the font size without a backtracking regex measureTextWidth's fallback read the font size with /(\d+(?:\.\d+)?)px/, which CodeQL flags as polynomial on long digit runs. Scan the shorthand's tokens instead; output is unchanged. * fix(blocks): keep overflow row and type-to-confirm state out of effects - use-properties-overflow: update the latest-inputs ref in a layout effect instead of during render, and disconnect the row's MutationObserver through the ref callback's cleanup. - PropertiesOverflowRow: close the +N menu while rendering when it is forced shut or nothing is collapsed, instead of a frame later in an effect. - TypeToConfirmDialog: clear the typed values once the close animation completes, instead of in an effect when it reopens. * fix(web): leave cancelled items out of the module list progress ring The list ring counted cancelled work items as done, so it disagreed with the module sidebar, which now measures completed / (total - cancelled). Use the same rule on the list. * fix(web): show an error when enabling or disabling a webhook fails The row menu's toggle discarded the rejected update, so the badge stayed as it was with no feedback. Catch it and show the existing "Webhook not updated" toast. * fix(web): unlock destructive confirms on the name only and translate them Delete project, leave project and delete workspace no longer require typing an English passphrase ("delete my project", "Leave Project", "delete my workspace"); typing the project or workspace name unlocks the action, as leave-workspace already did. Titles, descriptions, labels and toasts in these dialogs now go through t(), with new keys in every locale. Leaving a project also navigates to the projects list only after the API call succeeds, so a failed leave keeps the dialog open instead of moving the user away while they are still a member. * feat(blocks): add a wrapping work item properties list and a count chip WorkItemPropertiesList renders property entries in row order inside a wrapping container, the no-budget counterpart to PropertiesOverflowRow without its observers. WorkItemPropertyCountChip is the outlined sub-work item, attachment and link count chip: a button with onClick, or a display-only span without. * refactor(space): render public property chips through the blocks properties list Space's IssueProperties now builds the same keyed property entries as the web app and renders them with WorkItemPropertiesList, instead of a copied tree of per-property wrappers. Count chips use the shared WorkItemPropertyCountChip; web's board fallback and count chips use the same pieces. Chip order, gating and appearance are unchanged. --------- Co-authored-by: Sriram Veeraghanta <veeraghanta.sriram@gmail.com>
HaiYangBG1
requested review from
dheeru0198,
pablohashescobar,
pratapalakshmi and
saivallampati
as code owners
November 3, 2026 03:57
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The burndown tooltip renders ideal work-item counts at full floating-point precision, e.g.
8.571428571428573. Display the ideal series with at most one decimal place (8.6), while keeping integers and zero compact.Add an optional numeric tooltip formatter to
AreaChartand opt in only forProgressChart's ideal series. Current work-item/estimate values retain their precision, and chart data and other chart defaults stay unchanged.Type of Change
Test Scenarios
Current head
052f6e7318e3347fc3439da6d343ff4de26d313aincludespreviewatefa46d2840cee6872f838cb07fade205d05345c0. The tooltip uses upstream'sTooltipPopupand typography while retaining the optional numeric formatter. Callback parameters in the touched chart file were renamed to satisfy the current pre-commit no-shadow rule.Validation on macOS ARM64, Node 22.22.2 and pinned pnpm 11.10.0:
@plane/blocksunit/DOM suite: 36 files, 386 tests passed.git diff --check origin/previewpassed.The existing before/after browser proof shows the ideal tooltip changing from
8.571428571428573to8.6, with the current count remaining9and unchanged SVG curves. It predates this integration. The latest checks exercise the real updated chart component; the full authenticated application flow was not rerun.Hosted build/lint, CodeQL, React Doctor and copyright checks passed on this head. CodeRabbit's latest review reported no actionable comments. Maintainer review and merge remain separate.
References
Closes #8706.
Summary by CodeRabbit