mirror of
https://github.com/penpot/penpot.git
synced 2026-10-30 14:30:37 +00:00
* ✨ Add a branch count query for the workspace header The header shows its branch row only when the file has branches, and it learnt that from get-file-branches, which compares every diverged branch against main. get-file-branch-count answers the same question with one indexed count: archived and merged branches included, counted from main when the file is itself a branch, with no comparison and no climit, like the branches_count subquery of the dashboard listings. The listing keeps its climit, and :get-file-branches/global now sets the 60 s timeout that the header of climit.edn promises, so a waiter behind six cold listings gets an error instead of waiting without bound. Its rationale comment names the callers it serves now. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> * ✨ List branches only when the switcher opens Opening a file ran the counted branch listing for every user, because the header decided whether to show its branch row from that listing. A cold listing runs a three-way comparison per diverged branch, and one save to main makes every branch of the file a cache miss again. The header now loads the branch count and the branch context with the file. The listing loads when the branch switcher or the all-branches dialog opens, and both show "Loading branches…" until it arrives instead of a list from an earlier opening. A failed listing used to read as an empty list, which on main hid the branch row and sent the shortcut to the create dialog. It now ends in an error state: the switcher and the dialog say "Couldn't load branches" with a Retry action, and the row stays, since its visibility comes from the count. A successful listing refreshes the count, so deleting the last branch from the list still hides the row. Creating a pull request from the branch banner looked the branch up in the listing to decide whether to save pending edits first. Without the listing loaded at open it would skip the save, so it now reads the branch context of the open file. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> * ✨ Show the branch row when the branch count fails A failed get-file-branch-count left no count, so on main the header hid its branch row and the shortcut opened the create dialog on a file that may have branches. The failure is now kept as an error state under :workspace-branch-count, and has-branches? reads it as true. The row then shows the Main chip and, where the status note goes, "Couldn't load branches" in the warning colour. Opening the switcher loads the listing through the existing loading and Retry path, and a successful listing settles the count, which replaces the note. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> * 📚 Name the callers of the branch listing limit again The merge of iop-vc-preview took its climit.edn, whose comment on :get-file-branches/global still described a panel open. Restore the comment this branch wrote: the listing runs when the switcher, the all-branches dialog or the dashboard popover opens, and a file open only counts branches. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> * 🐛 Mock the branch count in the library copy tests The header now shows its branch row from get-file-branch-count, not from the listing, so the library copy tests mock the count as well. AI-assisted-by: claude-opus-5-5 Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro> --------- Signed-off-by: Álvaro Tejero Cantero <alvorithm@teje.ro>
204 lines
8.0 KiB
Clojure
204 lines
8.0 KiB
Clojure
;; Example climit.edn file
|
|
;; Required: permits
|
|
;; Optional: queue, omitted means Integer/MAX_VALUE (unbounded)
|
|
;; Optional: timeout in milliseconds, omitted means the thread waits for a
|
|
;; permit forever, and a saturated limiter parks it for good. Values below:
|
|
;; 60s for operations that can be slow (file updates, images, fonts, thumbnails,
|
|
;; snapshots, imports, branches, pull requests) and 30s for the rest.
|
|
;; Note: a queue bounds how many waiters pile up, a timeout bounds how long one
|
|
;; waiter waits. Every entry sets a timeout; the by-profile ones also set a
|
|
;; queue, which is where a pile-up hurts.
|
|
{:update-file/global
|
|
{:permits 20 :timeout 60000}
|
|
:update-file/by-profile
|
|
{:permits 1 :queue 5 :timeout 60000}
|
|
|
|
:process-font/global
|
|
{:permits 4 :timeout 60000}
|
|
:process-font/by-profile
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:process-image/global
|
|
{:permits 8 :timeout 60000}
|
|
:process-image/by-profile
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:auth/global
|
|
{:permits 8 :timeout 30000}
|
|
|
|
:root/global
|
|
{:permits 40 :timeout 30000}
|
|
|
|
:root/by-profile
|
|
{:permits 10 :queue 30 :timeout 30000}
|
|
|
|
:file-thumbnail-ops/global
|
|
{:permits 20 :timeout 60000}
|
|
:file-thumbnail-ops/by-profile
|
|
{:permits 2 :timeout 60000}
|
|
|
|
:submit-audit-events/by-profile
|
|
{:permits 1 :queue 3 :timeout 30000}
|
|
|
|
:restore-file-snapshot/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:restore-file-snapshot/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:create-file-snapshot/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:create-file-snapshot/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:send-user-feedback/global
|
|
{:permits 4 :timeout 30000}
|
|
|
|
:send-user-feedback/by-profile
|
|
{:permits 1 :queue 3 :timeout 30000}
|
|
|
|
:import-binfile/global
|
|
{:permits 4 :timeout 60000}
|
|
|
|
:import-binfile/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:create-import-binfile-job/global
|
|
{:permits 4 :timeout 60000}
|
|
|
|
:create-import-binfile-job/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
:profile-plugin-ops/global
|
|
{:permits 4 :timeout 30000}
|
|
|
|
:profile-plugin-ops/by-profile
|
|
{:permits 1 :queue 2 :timeout 30000}
|
|
|
|
;; Every limit below is provisional. The container that runs the preview
|
|
;; has no heap cap: `docker/devenv/files/entrypoint.sh` and
|
|
;; `backend/scripts/_env` both replace JAVA_OPTS without `-Xmx`, and the
|
|
;; live JVM command line carries none, so the budget is the JVM default
|
|
;; and it is unmeasured. The permit counts are derived from the
|
|
;; neighbours above instead of from a memory figure, and they stay
|
|
;; provisional until a peak-heap measurement of a cold compare on the
|
|
;; design-system file replaces them.
|
|
;;
|
|
;; The anchor is the snapshot pair above: `:create-file-snapshot/global`
|
|
;; admits three whole-file operations at once, each realizing one
|
|
;; document and writing one snapshot, and `:restore-file-snapshot/global`
|
|
;; admits the same three for an operation that also writes a rollback
|
|
;; snapshot and the older payload back into the file. A branch operation
|
|
;; realizes more than one document, so it has to admit fewer at once to
|
|
;; keep the same number of realized documents in flight.
|
|
;;
|
|
;; What each operation holds, read from the code:
|
|
;; * `create-file-branch` realizes main once and writes one snapshot
|
|
;; (`backend/src/app/rpc/commands/files_branch.clj::create-file-branch`),
|
|
;; which is the anchor's shape and the anchor's cost.
|
|
;; * `merge-file-branch` and `update-branch-from-main` hold three
|
|
;; realized documents at once: main, the derived branch, and the base
|
|
;; snapshot (`files_branch.clj::merge-file-branch`,
|
|
;; `files_branch.clj::update-branch-from-main`). Both also write a
|
|
;; snapshot and a whole file payload, and the update computes a
|
|
;; second diff for the squash, so they earn the tightest count.
|
|
;; * `materialize-file-branch` realizes the branch and the libraries it
|
|
;; validates against, then writes the whole payload
|
|
;; (`files_branch.clj::materialize-file-branch`), so it sits between
|
|
;; the anchor and the merge pair.
|
|
;; * `create-pull-request` and `update-pull-request-snapshot` realize the
|
|
;; branch once and write one review snapshot
|
|
;; (`files_pull_request.clj::create-pull-request`,
|
|
;; `files_pull_request.clj::update-pull-request-snapshot`), which is
|
|
;; the anchor's shape again.
|
|
;;
|
|
;; The write-side operations carry a queue so that a refusal follows a
|
|
;; bounded wait rather than arriving instantly. The queue sits on the
|
|
;; per-profile entry, following the anchor, and on the global entry
|
|
;; where the command declares no per-profile queue. The per-profile
|
|
;; entry of one permit keeps a single designer to one operation at a
|
|
;; time. The two read-side entries are generous by design and carry no
|
|
;; queue, so a panel open waits for its turn instead of being refused:
|
|
;; six three-document comparisons hold eighteen realized documents,
|
|
;; fewer than the twenty one-document saves `:update-file/global`
|
|
;; already admits.
|
|
;;
|
|
;; Every entry below, global and per-profile, read and write, takes the
|
|
;; 60 s timeout of the slow operations above. The size gates in
|
|
;; `backend/src/app/rpc/commands/files_branch.clj` refuse any file large
|
|
;; enough for an operation to exceed 30 s, with the measured limit
|
|
;; halved, so a 60 s wait covers one in-flight operation at that
|
|
;; ceiling with as much again in margin.
|
|
|
|
;; create-file-branch realizes main and writes one snapshot, the shape
|
|
;; of `:create-file-snapshot` above, so it takes the anchor's numbers
|
|
;; unchanged. Provisional.
|
|
:create-file-branch/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:create-file-branch/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; A merge holds three realized documents, a snapshot write, and a
|
|
;; whole-file write into main, so one instance-wide operation holds the
|
|
;; anchor's whole three-permit budget on its own. Provisional.
|
|
:merge-file-branch/global
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:merge-file-branch/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; An update from main holds the same three documents and pays for a
|
|
;; snapshot write and a squash diff on top, so it takes the merge's
|
|
;; count. Provisional.
|
|
:update-branch-from-main/global
|
|
{:permits 1 :timeout 60000}
|
|
|
|
:update-branch-from-main/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; Materialize realizes one document plus its libraries and writes the
|
|
;; payload, which is cheaper than a merge and dearer than the anchor,
|
|
;; so it admits two where the merge admits one and the anchor admits
|
|
;; three. Provisional.
|
|
:materialize-file-branch/global
|
|
{:permits 2 :timeout 60000}
|
|
|
|
:materialize-file-branch/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; A cold listing realizes main once and derives every uncached branch,
|
|
;; which makes it the slowest read of the preview. It runs when a user
|
|
;; opens the workspace branch switcher, the all-branches dialog, or the
|
|
;; dashboard branches popover; a file open only counts branches, through
|
|
;; `get-file-branch-count`, which takes no limit. Six at once is above
|
|
;; what those opens need under normal use, and six three-document
|
|
;; operations hold eighteen realized documents, below the twenty
|
|
;; one-document saves `:update-file/global` above already admits.
|
|
;; Provisional.
|
|
:get-file-branches/global
|
|
{:permits 6 :timeout 60000}
|
|
|
|
;; The compare dialog's query holds main, the derived branch, and the
|
|
;; base at once, the same three documents the listing holds, so it takes
|
|
;; the same count. Provisional.
|
|
:get-branch-diff/global
|
|
{:permits 6 :timeout 60000}
|
|
|
|
;; Creating a pull request realizes the branch and pins one review
|
|
;; snapshot, the anchor's shape. Provisional.
|
|
:create-pull-request/global
|
|
{:permits 3 :timeout 60000}
|
|
|
|
:create-pull-request/by-profile
|
|
{:permits 1 :queue 2 :timeout 60000}
|
|
|
|
;; Publishing the branch state also realizes the branch and pins one
|
|
;; snapshot. The command declares a single instance-wide queue
|
|
;; (`files_pull_request.clj::update-pull-request-snapshot`), so the
|
|
;; bounded wait sits on the global entry here rather than on a
|
|
;; per-profile entry. Provisional.
|
|
:update-pull-request-snapshot/global
|
|
{:permits 3 :queue 4 :timeout 60000}}
|