port: attachments (approved mockup) 1/3: images #846

Open
kayg wants to merge 15 commits from port/port-attachments into dev
Owner

Update: first-paint budget (#866)

perf/web.sh --bundle-only --gate (from perf/budgets-per-app, #866, PERF_DIST = this branch's build with #866's vite config) first said first_js_brotli 242.147 KB > 240. The picture widget and the attach code are now out of the entry chunk:

  • attachments lazy chunk (3.09 KB brotli): add/paste/drop (attach.ts, upload.ts, one page listener for pasted/dropped files), the sizes the server knows (meta.ts), the picture widget class, the viewer's opener, the sizes cache. Loaded when a note with a picture opens, when someone first uses Attach or the Insert grid's Image/File, and once the app is idle after the first paint (like the other lazy screens), so a pasted file is taken at once.
  • Entry keeps only: a imageWidget facet and a check in hidden.ts that hands a line's text to the app (it draws nothing until the chunk is here), the 60-line shim lib/attachments/lazy.ts (load, widget, pictures, attachMenu), the Attach button, a paperclip icon path, and the wait for the chunk and the sizes before a note with pictures draws (so its boxes have their final shape and nothing jumps).
  • The camera icon's path comes with the chunk (addIcon); the viewer's state lives in PictureViewer.svelte's module script and the chunk mounts it itself; the "menu from a button at the foot of the screen opens above it" placement moved into ContextMenu (already lazy), so menu.ts/ui.svelte.ts carry nothing new.
  • Result (brotli KB, entry chunk = first paint): dev 239.517; this PR before 242.147 (over); this PR now 239.943 (under 240, +0.43 over dev). Everything else in the table is ok. The one line the gate still reports is the new chunk name: web.bundle.chunk.attachments (3.088 KB) needs a budget line (I used "p95": 3.25, "initial": false to see it green, not committed since perf/budgets.json is #866's), and total_js_brotli must then equal the new sum of chunk budgets (the measured total, 354.7 KB, is far under the 404.2 limit).
  • Notes: entry size moves ±100 bytes with chunk-file hashes embedded in it, so the margin is about 60 bytes; moving this PR's stylesheet into the chunk made the entry bigger (the chunk's CSS dependency is listed in the entry), so it stays in the first-paint CSS (first_css is under its limit). Behaviour is unchanged: pictures.spec + bar-stable + phone-bars + editor/links/picker specs pass on Chromium and WebKit at 390 and 1440 (250 each for the pictures set); editor 517 / web 86 unit tests, tsc and svelte-check clean.

port: attachments, slice 1 of 3 (images) · stacked: #1 → 2 → 3

Today a note cannot hold a picture. This adds it: Attach → Photos / Camera / File, drop and paste on the text, an inline picture in the mockup's width, and a viewer on tap. Mockup: review/calendar-2 (blocks/plugins/media.ts, embeds.css, the Attach menu), approved G11/G12, #471/#172.

What it does

  • Upload through the existing POST /api/v1/attachments?name=&date=. The note gets its line only after the server answers, so the markdown never points at a missing file.
  • On disk (ADR 0024, nothing invented): ![name](<../Photos/YYYY/YYYY-MM-DD/name.png>) alone on a line. The same bytes twice: the server's dedupe returns the existing path ("Already uploaded"), the line reuses it.
  • Display (packages/editor/src/syntax/embeds.ts): a line that is only an embed link becomes one atomic piece in default mode; anywhere else (words on the line, web image, code, list, quote) it stays text. The editor asks the app for the address and size through a facet, like links.
  • Gaps fixed (review feedback): the line has no text height of its own and CodeMirror's empty buffer boxes are gone (.cm-embed-line: line-height:0; font-size:0), so a picture has the same room around it as a paragraph (asserted in the spec: gap ≤ paragraph gap).
  • Attach menu (review feedback): the app's one in-place menu (ContextMenu), now with an up placement: it grows out of the Attach button's top edge, centred, from a bar at the foot of the screen. Photos and File everywhere; Camera only on a coarse pointer (touch).
  • Sizes on disk, so the first view never shifts (review feedback): new action attachments.meta (crates/attachments/src/meta.rs): for up to 200 paths in the caller's Home it reads, from the files themselves (same rules as serving: servable shelves only, opened strictly inside the Home, type from content), the size, type, shelf and a picture's width/height from its header (and a PDF's page count, best effort, ≤8 MB). Nothing is stored. The web asks in batches as soon as a note's text is known (peek), a first view waits at most 250 ms for the answer (once per picture on a device, never for notes without pictures), and sizes are remembered on the device. Rust test meta_gives_a_pictures_size_and_skips_what_is_not_servable (a known picture, a missing file, a note path and a .. path). contracts/actions.json and actions.gen.ts regenerated.
  • Viewer (lazy chunk): grows out of the tapped box and back; Esc, tap or ✕ closes; reduced motion fades. It takes Esc only while a picture is open.
  • Insert grid: Image and File tiles work (they said "Comes with attachments").

phone-bars limit (review feedback)

The earlier NOTE_PHOTO.chromium 15.7 → 16.0 bump is reverted. Cause: the note tools gained Attach, and rows() took the median of a pixel row that runs through the app's own floating bar, so the bar's width decided the median. The spec now measures that row beside the bar (the bar's own columns left out), and the original 15.7 / 7.8 limits hold unchanged on Chromium and WebKit.

Gates (dev-01 Chromium, dev-02 WebKit, CI=1 --retries=0, ~/bin/slot; head 8643313b)

  • editor vitest run 517 pass, tsc --noEmit ok; web vitest run 86 pass; svelte-check 0 errors; cargo test -p calternal-attachments 21 pass; cargo test -p calternal contract ok.
  • e2e/pictures.spec.ts (4 tests: Attach→Photos + viewer, paste + reload without a jump, same picture twice + source mode, first-frame shape + spacing) + bar-stable + phone-bars, chromium-1440/390: 250 pass; webkit-1440/390: 250 pass (one earlier full WebKit run had 3 intermittent failures in the pictures spec under load; rerun 24/24 and the full set 250/250).
  • Round trip: the text is never rewritten (unit test: document byte-identical; spec reads the file on the server).

Perf (dev VM, indicative; perf-VM harness not run)

Bundle gz vs dev: first paint +3.3 KB, all +4.7 KB (this slice). Whole stack:

gz bytes first paint (entry) all JS+CSS
dev 314,236 460,562
slice 1 317,511 (+3.3 KB) 465,238 (+4.7 KB)
slice 2 323,395 (+9.2 KB) 471,534 (+11.0 KB)
slice 3 327,810 (+13.6 KB) 476,079 (+15.5 KB)

Note with 20 paragraphs + 20 × 1200×800 noise PNGs vs 20 paragraphs alone (Chromium 1440, slice 3 head): ready 277–370 vs 270–503 ms, text visible 293–522 vs 283–518 ms, 5 keys typed right after open 255–303 vs 268–277 ms, long tasks 0 vs 0–187 ms, CLS 0.0008–0.0021 vs 0.0016–0.0018; only the 2 on-screen images load (lazy, decoding=async).

Screenshots

Control host: ~/Developer/Worktrees/shots/port-attachments/ (product-* ↔ mockup-*, 1440x900 and 390x844).

Not in this slice

Everything in #2 and #3 (name strip's editing, galleries, files, players, note embeds, mini bar).

🤖 Generated with Claude Code

Budget (kayg approved, 2026-11-08 08:20 IST): kayg approved raising the lazy budget total, up to 543.9 KiB. perf/budgets.json in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.

### Update: first-paint budget (#866) `perf/web.sh --bundle-only --gate` (from `perf/budgets-per-app`, #866, `PERF_DIST` = this branch's build with #866's vite config) first said **first_js_brotli 242.147 KB > 240**. The picture widget and the attach code are now out of the entry chunk: - **`attachments` lazy chunk** (3.09 KB brotli): add/paste/drop (`attach.ts`, `upload.ts`, one page listener for pasted/dropped files), the sizes the server knows (`meta.ts`), the picture widget class, the viewer's opener, the sizes cache. Loaded when a note with a picture opens, when someone first uses Attach or the Insert grid's Image/File, and once the app is idle after the first paint (like the other lazy screens), so a pasted file is taken at once. - **Entry keeps** only: a `imageWidget` facet and a check in `hidden.ts` that hands a line's text to the app (it draws nothing until the chunk is here), the 60-line shim `lib/attachments/lazy.ts` (`load`, `widget`, `pictures`, `attachMenu`), the Attach button, a paperclip icon path, and the wait for the chunk and the sizes before a note with pictures draws (so its boxes have their final shape and nothing jumps). - The camera icon's path comes with the chunk (`addIcon`); the viewer's state lives in `PictureViewer.svelte`'s module script and the chunk mounts it itself; the "menu from a button at the foot of the screen opens above it" placement moved into `ContextMenu` (already lazy), so `menu.ts`/`ui.svelte.ts` carry nothing new. - **Result** (brotli KB, entry chunk = first paint): `dev` 239.517; this PR before **242.147** (over); this PR now **239.943** (under 240, +0.43 over `dev`). Everything else in the table is `ok`. The one line the gate still reports is the **new chunk name**: `web.bundle.chunk.attachments` (3.088 KB) needs a budget line (I used `"p95": 3.25, "initial": false` to see it green, not committed since `perf/budgets.json` is #866's), and `total_js_brotli` must then equal the new sum of chunk budgets (the measured total, 354.7 KB, is far under the 404.2 limit). - Notes: entry size moves ±100 bytes with chunk-file hashes embedded in it, so the margin is about 60 bytes; moving this PR's stylesheet into the chunk made the entry *bigger* (the chunk's CSS dependency is listed in the entry), so it stays in the first-paint CSS (first_css is under its limit). Behaviour is unchanged: `pictures.spec` + `bar-stable` + `phone-bars` + editor/links/picker specs pass on Chromium and WebKit at 390 and 1440 (250 each for the pictures set); editor 517 / web 86 unit tests, `tsc` and `svelte-check` clean. ## port: attachments, slice 1 of 3 (images) · stacked: #1 → 2 → 3 Today a note cannot hold a picture. This adds it: **Attach → Photos / Camera / File**, drop and paste on the text, an inline picture in the mockup's width, and a viewer on tap. Mockup: review/calendar-2 (`blocks/plugins/media.ts`, `embeds.css`, the Attach menu), approved G11/G12, #471/#172. ### What it does - **Upload** through the existing `POST /api/v1/attachments?name=&date=`. The note gets its line only after the server answers, so the markdown never points at a missing file. - **On disk** (ADR 0024, nothing invented): `![name](<../Photos/YYYY/YYYY-MM-DD/name.png>)` alone on a line. The same bytes twice: the server's dedupe returns the existing path ("Already uploaded"), the line reuses it. - **Display** (`packages/editor/src/syntax/embeds.ts`): a line that is only an embed link becomes one atomic piece in default mode; anywhere else (words on the line, web image, code, list, quote) it stays text. The editor asks the app for the address and size through a facet, like links. - **Gaps fixed** (review feedback): the line has no text height of its own and CodeMirror's empty buffer boxes are gone (`.cm-embed-line`: `line-height:0; font-size:0`), so a picture has the same room around it as a paragraph (asserted in the spec: gap ≤ paragraph gap). - **Attach menu** (review feedback): the app's one in-place menu (`ContextMenu`), now with an `up` placement: it grows out of the Attach button's top edge, centred, from a bar at the foot of the screen. Photos and File everywhere; Camera only on a coarse pointer (touch). - **Sizes on disk, so the first view never shifts** (review feedback): new action **`attachments.meta`** (`crates/attachments/src/meta.rs`): for up to 200 paths in the caller's Home it reads, from the files themselves (same rules as serving: servable shelves only, opened strictly inside the Home, type from content), the size, type, shelf and a picture's width/height from its header (and a PDF's page count, best effort, ≤8 MB). Nothing is stored. The web asks in batches as soon as a note's text is known (`peek`), a first view waits at most 250 ms for the answer (once per picture on a device, never for notes without pictures), and sizes are remembered on the device. Rust test `meta_gives_a_pictures_size_and_skips_what_is_not_servable` (a known picture, a missing file, a note path and a `..` path). `contracts/actions.json` and `actions.gen.ts` regenerated. - **Viewer** (lazy chunk): grows out of the tapped box and back; Esc, tap or ✕ closes; reduced motion fades. It takes Esc only while a picture is open. - Insert grid: Image and File tiles work (they said "Comes with attachments"). ### phone-bars limit (review feedback) The earlier `NOTE_PHOTO.chromium` 15.7 → 16.0 bump is **reverted**. Cause: the note tools gained Attach, and `rows()` took the median of a pixel row that runs *through* the app's own floating bar, so the bar's width decided the median. The spec now measures that row beside the bar (the bar's own columns left out), and the original 15.7 / 7.8 limits hold unchanged on Chromium and WebKit. ### Gates (dev-01 Chromium, dev-02 WebKit, `CI=1 --retries=0`, `~/bin/slot`; head `8643313b`) - editor `vitest run` 517 pass, `tsc --noEmit` ok; web `vitest run` 86 pass; `svelte-check` 0 errors; `cargo test -p calternal-attachments` 21 pass; `cargo test -p calternal contract` ok. - `e2e/pictures.spec.ts` (4 tests: Attach→Photos + viewer, paste + reload without a jump, same picture twice + source mode, first-frame shape + spacing) + `bar-stable` + `phone-bars`, chromium-1440/390: 250 pass; webkit-1440/390: 250 pass (one earlier full WebKit run had 3 intermittent failures in the pictures spec under load; rerun 24/24 and the full set 250/250). - Round trip: the text is never rewritten (unit test: document byte-identical; spec reads the file on the server). ### Perf (dev VM, indicative; perf-VM harness not run) Bundle gz vs `dev`: first paint +3.3 KB, all +4.7 KB (this slice). Whole stack: | gz bytes | first paint (entry) | all JS+CSS | |---|---|---| | `dev` | 314,236 | 460,562 | | slice 1 | 317,511 (+3.3 KB) | 465,238 (+4.7 KB) | | slice 2 | 323,395 (+9.2 KB) | 471,534 (+11.0 KB) | | slice 3 | 327,810 (+13.6 KB) | 476,079 (+15.5 KB) | Note with 20 paragraphs + 20 × 1200×800 noise PNGs vs 20 paragraphs alone (Chromium 1440, slice 3 head): ready 277–370 vs 270–503 ms, text visible 293–522 vs 283–518 ms, 5 keys typed right after open 255–303 vs 268–277 ms, long tasks 0 vs 0–187 ms, CLS 0.0008–0.0021 vs 0.0016–0.0018; only the 2 on-screen images load (lazy, `decoding=async`). ### Screenshots Control host: `~/Developer/Worktrees/shots/port-attachments/` (`product-*` ↔ `mockup-*`, 1440x900 and 390x844). ### Not in this slice Everything in #2 and #3 (name strip's editing, galleries, files, players, note embeds, mini bar). 🤖 Generated with [Claude Code](https://claude.com/claude-code) **Budget (kayg approved, 2026-11-08 08:20 IST):** kayg approved raising the lazy budget total, up to 543.9 KiB. `perf/budgets.json` in this PR sets each named lazy line to the largest size measured over the 17 approved ports after trimming (rounded up to 0.01 KiB) and the total to the sum of the lines (543.25 KiB). The same file is in the first PR of every stack, so whichever merges first carries it. First paint is judged separately and has not been raised.
kayg added 15 commits 2026-11-02 09:34:49 +00:00
Author
Owner

housekeeping #1009: rebased+failing, left open for review.

rebased attachment slice one while keeping dev App, toolbar, tasks/contacts, pull hint and icon registry. chooser remains synchronously invoked from the tap after lazy loading; specs follow current desktop/phone menus. updated current icon registration and AttachMenu narrowing; fixed current Caller credential and PDF lint.

head: c75d2a495473934381e5c00e14ade2d2a431b7d8; based on dev cd7f84432febfc63b788fbb9b9f9950452982d74, pushed with the original exact force-with-lease.

frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 231037 B (+260 B), CSS 18516 B (+130 B).

Rust gate results (Mac VM):

exit command
0 cargo fmt -p calternal-attachments -- --check
0 cargo check -j4 --locked -p calternal-attachments --all-targets
0 cargo clippy -j4 --locked -p calternal-attachments --all-targets -- -D warnings
0 cargo test -j4 --locked -p calternal-attachments

targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun.

all executed gates used the VM Nix environment, shared Mac lock and slot. exact commands, source heads, logs and dev baseline. frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head.

original-head cherry/patch inventory, pr-846.md; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above.

final queue, cleanup and source-verification evidence.

housekeeping #1009: rebased+failing, left open for review. rebased attachment slice one while keeping dev App, toolbar, tasks/contacts, pull hint and icon registry. chooser remains synchronously invoked from the tap after lazy loading; specs follow current desktop/phone menus. updated current icon registration and AttachMenu narrowing; fixed current Caller credential and PDF lint. head: `c75d2a495473934381e5c00e14ade2d2a431b7d8`; based on dev `cd7f84432febfc63b788fbb9b9f9950452982d74`, pushed with the original exact force-with-lease. frontend frozen install, check, lint, build and unit tests passed. bundle gate failed. first-paint brotli vs dev: JS 231037 B (+260 B), CSS 18516 B (+130 B). Rust gate results (Mac VM): | exit | command | |---|---| | 0 | `cargo fmt -p calternal-attachments -- --check` | | 0 | `cargo check -j4 --locked -p calternal-attachments --all-targets` | | 0 | `cargo clippy -j4 --locked -p calternal-attachments --all-targets -- -D warnings` | | 0 | `cargo test -j4 --locked -p calternal-attachments` | targeted Chromium and WebKit specs at 1440 and 390 never started. the audit waited more than three hours behind the live daily-train-1107 locks (first queue at 23:45 IST, 7 November; final queue cleanup after 02:46 IST, 8 November). browser validation and UI captures are unrun. only this audit’s exact queued units were stopped; the train locks and owners were left intact. hot-path perf qualification is unrun. all executed gates used the VM Nix environment, shared Mac lock and slot. [exact commands, source heads, logs and dev baseline](https://git.kayg.org/attachments/870a0fef-2e31-425c-aea6-eb87a66d365a). frontend inputs are identical to the tested frontend tree when a Rust-only repair changed the head. [original-head cherry/patch inventory](https://git.kayg.org/attachments/e05a38dc-c028-4d77-8655-efa9d5c8ea50), `pr-846.md`; unmatched entries are missing-patch candidates, with semantic overlap/replacements noted above. [final queue, cleanup and source-verification evidence](https://git.kayg.org/attachments/7ab315a5-4b6c-4af1-971b-cdae2637adae).
Author
Owner

Final-port test pass (issue #1009), READY: no: 1 real failure in both browsers; bundle gate fails.

head c75d2a495473934381e5c00e14ade2d2a431b7d8

Targeted specs, CI=1 --retries=0 --workers=1, dev-01 Chromium and dev-02 WebKit (through ~/bin/slot), against a dev-head backend (cd7f84432).

spec Chromium 1440 Chromium 390 WebKit 1440 WebKit 390
pictures, phone-bars 3 pass, 1 fail 243 pass, 1 fail 3 pass, 1 fail 243 pass, 1 fail

Failing: pictures.spec.ts:126 the same picture twice is one file... times out at getByRole('button', { name: 'More' }) (line 139).

Bundle gate: perf/web.sh --bundle-only --gate (the 17 failures from the housekeeping audit). Per the gate's own rule (total_js_brotli budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing.

  • Dev's pill toolbar (#912) has no 'More' button on desktop; Show markdown moved. The spec still drives the old toolbar. Fixing it means deciding where the desktop path goes (spec change), so I left it.
  • Gate: attachments chunk (2.66 KB) has no budget.

Not done: Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment).

Not marked READY FOR MERGE.

**Final-port test pass (issue #1009), READY: no: 1 real failure in both browsers; bundle gate fails.** head `c75d2a495473934381e5c00e14ade2d2a431b7d8` Targeted specs, `CI=1 --retries=0 --workers=1`, dev-01 Chromium and dev-02 WebKit (through `~/bin/slot`), against a dev-head backend (`cd7f84432`). | spec | Chromium 1440 | Chromium 390 | WebKit 1440 | WebKit 390 | |---|---|---|---|---| | pictures, phone-bars | 3 pass, 1 fail | 243 pass, 1 fail | 3 pass, 1 fail | 243 pass, 1 fail | Failing: `pictures.spec.ts:126 the same picture twice is one file...` times out at `getByRole('button', { name: 'More' })` (line 139). **Bundle gate:** `perf/web.sh --bundle-only --gate` (the 17 failures from the housekeeping audit). Per the gate's own rule (`total_js_brotli` budget must equal the sum of the named chunk budgets) a cap can only move between named chunks; there is no donor chunk inside this PR, so a fix is a code cut or a decision by kayg. I raised nothing and moved nothing. - Dev's pill toolbar (#912) has no 'More' button on desktop; Show markdown moved. The spec still drives the old toolbar. Fixing it means deciding where the desktop path goes (spec change), so I left it. - Gate: `attachments` chunk (2.66 KB) has no budget. **Not done:** Not run yet: Mac VM Safari blur/frame check and perf ABAB x5 on the perf VMs (they need a ready PR first); light/dark 2x screenshots against the mockup are only done for #888 (see its comment). Not marked READY FOR MERGE.
Author
Owner

READY FOR MERGE: attachments 1, pictures (head 2dfcb618d)

Merged dev fe2efbf11 (#1044) into the branch (clean; it also merges cleanly with the newer 4274dafb4), budgets regenerated from a fresh build, bundle gate clean, specs on dev-02 with calternal-h861, chromium and webkit at 1440 and 390. Dev baseline: 228023 B plain / 227378 B normalised / CSS 18236.

first paint plain / normalised CSS budget total
228313 / 227544 B (+290 / +166 B; growth accepted by kayg) 18406 (+170) 534.56 KiB

Specs (preload, lazy-ui, lazy-offline, glass, shortcuts, pictures): 128 passed, 36 skipped, 0 failed.

## READY FOR MERGE: attachments 1, pictures (head `2dfcb618d`) Merged dev `fe2efbf11` (#1044) into the branch (clean; it also merges cleanly with the newer `4274dafb4`), budgets regenerated from a fresh build, bundle gate clean, specs on dev-02 with `calternal-h861`, chromium and webkit at 1440 and 390. Dev baseline: 228023 B plain / 227378 B normalised / CSS 18236. | first paint plain / normalised | CSS | budget total | |---|---|---| | 228313 / 227544 B (**+290 / +166 B**; growth accepted by kayg) | 18406 (+170) | 534.56 KiB | Specs (preload, lazy-ui, lazy-offline, glass, shortcuts, pictures): 128 passed, 36 skipped, 0 failed.
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
Merge conflict checking is in progress. Try again in few moments.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin port/port-attachments:port/port-attachments
git switch port/port-attachments

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch dev
git merge --no-ff port/port-attachments
git switch port/port-attachments
git rebase dev
git switch dev
git merge --ff-only port/port-attachments
git switch port/port-attachments
git rebase dev
git switch dev
git merge --no-ff port/port-attachments
git switch dev
git merge --squash port/port-attachments
git switch dev
git merge --ff-only port/port-attachments
git switch dev
git merge port/port-attachments
git push origin dev
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal!846
No description provided.