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) {
+
+