From be1c1860b318292efa5883502796b5e0b960c480 Mon Sep 17 00:00:00 2001 From: Phil Merrell Date: Wed, 16 Sep 2026 16:46:12 -0600 Subject: [PATCH] feat(spa): share artifacts from the library and the owner view page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sharing an artifact used to be reachable only from the card inside the conversation that produced it, which meant the library at /artifacts — the page whose whole premise is that artifacts outlive their chat — had no way to share one. Both surfaces now open the same ArtifactShareModalComponent the card does, so there stays exactly one place in the app that knows what an artifact share is. The control is gated on `row.owned`, so it appears on your own rows and never on the "Shared with you" ones: a received artifact has no artifact id to share (the share id is the only handle you have on it), and re-sharing someone else's grant is not something this model supports. The view page deliberately has no ownership check of its own. It resolves its artifact out of listLibrary(), which reads the caller's own DynamoDB partition, so an id that is not yours renders "Artifact not found" before the header exists at all — the boundary is the partition key, and a comparison in the template would imply the guarantee lives in the UI. A test pins that the control is absent when nothing resolved. Both surfaces show HEAD, so they pin HEAD's version. Shares are immutable and never follow HEAD, so the version rides in each control's accessible name ("Share Quarterly plan, version 4") as well as the dialog's caption — the library prints a version badge only when it is > 1, so the control cannot rely on one being on screen. Grid footer: raise the label-collapse threshold 19rem -> 21rem. Found in the browser, not by a test. 19rem was tuned for FOUR controls and fit with zero slack: at three columns on a 1280px window the container measures exactly 306px and the labelled row needed exactly 306px. Adding Share pushed the row to 326px while leaving the container 2px ABOVE the collapse threshold, so the labels stayed visible and the Delete icon was clipped. 21rem (336px) collapses them with 10px to spare, and the labels still reappear at one column, so they are hidden where they do not fit rather than removed. Verified against real data on dev: zero overflow on every card, in both views. Co-Authored-By: Claude Opus 5 --- .../app/artifacts/artifact-library.page.html | 53 +++++++++-- .../artifacts/artifact-library.page.spec.ts | 90 ++++++++++++++++++- .../app/artifacts/artifact-library.page.ts | 40 ++++++++- .../src/app/artifacts/artifact-view.page.html | 16 ++++ .../app/artifacts/artifact-view.page.spec.ts | 58 ++++++++++++ .../src/app/artifacts/artifact-view.page.ts | 38 ++++++++ 6 files changed, 284 insertions(+), 11 deletions(-) diff --git a/frontend/ai.client/src/app/artifacts/artifact-library.page.html b/frontend/ai.client/src/app/artifacts/artifact-library.page.html index fab177482..33cf9eece 100644 --- a/frontend/ai.client/src/app/artifacts/artifact-library.page.html +++ b/frontend/ai.client/src/app/artifacts/artifact-library.page.html @@ -334,6 +334,23 @@

No artifact Open {{ row.title }} @if (row.owned; as item) { + + +