feat(spa): share artifacts from the library and the owner view page - #1144
Open
philmerrell wants to merge 1 commit into
Open
philmerrell wants to merge 1 commit into
philmerrell wants to merge 1 commit into
Conversation
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 <noreply@anthropic.com>
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.
What
Sharing an artifact was reachable only from the card inside the conversation that produced it. The library at
/artifacts— the page whose whole premise is that artifacts outlive their chat — had no way to share one, and neither did the owner view page at/artifacts/:id.Both surfaces now open the same
ArtifactShareModalComponentthe card opens, so there stays exactly one place in the app that knows what an artifact share is.@if (row.owned; as item)blockml-autoicon group, beside Rename/DeleteDesign notes
Owner-only, by structure rather than by check. In the library the control is gated on
row.owned, so it never renders on a "Shared with you" row: 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; a comparison in the template would imply the guarantee lives in the UI. A test pins that the control is absent when nothing resolved.Version semantics. Both surfaces show HEAD, so they pin HEAD's version — and shares are immutable, never following HEAD. The version therefore rides in each control's accessible name (
Share Quarterly plan, version 4) as well as the dialog's caption, because the library prints a version badge only when it is> 1and the control cannot rely on one being on screen.The grid-footer regression this surfaced
Found in the browser, not by a test.
@max-[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.Raised to
@max-[21rem](336px), which collapses them with 10px to spare. Attributed by measurement, not inference: hiding the Share button in the live DOM dropped the overflow from 20px back to 0. The labels still reappear at one column (36px / 90px), so they are hidden where they do not fit rather than removed — the failure mode where "the overlap is gone" because the content is gone.The spec now pins 21rem with a comment saying it is a measured number and to re-measure before adding a sixth control.
Verification
Full SPA suite green: 268 files, 3363 tests. Six new tests; the version-label one was mutation-checked (removed the version from the template, confirmed it failed with the right message), since DOM-selector tests are the kind that can pass vacuously.
Verified live against real dev data (12 artifacts, one of them genuinely shared with this account):
enrollment-report.js · version 1and firesGET /artifacts/{id}/shares → 200, so the artifact id reaches the real backend correctly.aria-label="Share this artifact, version 1".dark:text-gray-400token as the Download and new-tab controls beside it.No share link was actually created — a live link is outward-facing and this is a real dev account. The create path is the same dialog already validated live in #920.
Scope
Frontend only. No backend, API, or infra change —
ArtifactShareServiceand every endpoint behind it already existed.🤖 Generated with Claude Code