feat(artifacts): All / Yours / Shared with you tabs on the library - #970
Merged
Merged
Conversation
PR-2 of the share-inbox pair. #968 gave recipients somewhere to be listed; this is the surface that lists them. ## One row shape, two records A received artifact is not a `LibraryArtifact` with extra fields. It has no artifact id and no session to go back to — the share id is the only handle a recipient has on it — and no `updatedAt` that means anything to them, because that is the owner's clock. Modelling both as one type would invite a template to reach for a field that is structurally absent on half its rows. So both lists are normalized into a `LibraryRow` with a `kind` discriminator, and the template branches on that and nothing else. Owner actions (rename, delete, jump to conversation) render only for `owned`; a received card opens at /shared-artifact/{shareId} and stops there, which is what the recipient endpoints enforce server-side anyway. The thumbnail gains the same discrimination. A recipient cannot mint against an artifact id — the owner endpoint builds its key from the session and would 404 — so the source says which credential it is rather than the component guessing from which fields are populated. ## The flag reaches the SPA without any new plumbing `listSharedWithMe` returns null on 404, which is the backend saying the inbox does not exist in this environment. Null hides the tabs entirely; `[]` is a real, empty inbox and says "nothing yet". Collapsing the two would either show a permanently empty tab wherever the feature is off or hide a genuine empty state. Anything that is not a 404 rethrows — "we could not load your inbox" is not "you do not have one". The two lists load with `allSettled`, not `all`: your own library is the page's reason to exist, so an inbox that 503s must not blank it. The tabs simply do not appear, exactly as when the feature is off. ## Notes "All" interleaves both lists newest-first. That is the one place this page sorts, and it is a deliberate exception to its own no-re-sorting rule: two independently server-ordered lists cannot be shown as one without merging them on the client. The single-list tabs do not sort. Search matches the sender on a received row — "who sent me that thing" is at least as likely a starting point as remembering its title. "Load more" is inbox-only (the owned library endpoint is unpaginated) and stays visible on the All tab, because the merged list is only as complete as what has been loaded and hiding the control there would present a partial merge as the whole thing. ## Fixes a contrast defect found while verifying The tabs were first built as an underline in `primary-accessible`, which is a fixed brand colour with no dark variant: a 2px #0033A0 rule on the #101828 dark surface is close to invisible. Switched to the filled pill the model-catalog tabs and this page's own view toggle already use — 10.6:1 on the active pill, and one fewer idiom in the app. Verified in the browser against the compiled stylesheet at 1080px in both themes: tabs, received-card variant, and no horizontal overflow. SPA suite: 2341 passed, +17 from this change. The one failure in a full run (`shared-view.page.spec.ts`, a parallel-load timeout its own comment documents) reproduces on clean develop with these changes stashed. 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.
PR-2 of the share-inbox pair. #968 gave recipients somewhere to be listed; this is the surface that lists them. Frontend only.
Still dark: with
ARTIFACT_SHARE_INBOX_ENABLEDoff the endpoint 404s, no tabs render, and the library is exactly what it is today.One row shape, two records
A received artifact is not a
LibraryArtifactwith extra fields. It has no artifact id and no session to go back to — the share id is the only handle a recipient has on it — and noupdatedAtthat means anything to them, because that's the owner's clock. Modelling both as one type would invite a template to reach for a field that's structurally absent on half its rows.So both lists normalize into a
LibraryRowcarrying akinddiscriminator, and the template branches on that and nothing else:/artifacts/{id}/shared-artifact/{shareId}ArtifactThumbnailComponentgains the same discrimination. A recipient can't mint against an artifact id — the owner endpoint builds its DynamoDB key from the session and would 404 — so the source says which credential it is rather than the component inferring it from which fields are populated. Two mint paths, one component, mirroring howArtifactViewerComponentalready serves the owner panel and the recipient page.The flag reaches the SPA with no new plumbing
listSharedWithMereturns null on 404 — the backend saying the inbox doesn't exist here. Null hides the tabs;[]is a real, empty inbox and says "nothing yet". Collapsing those would either show a permanently empty tab wherever the feature is off, or hide a genuine empty state. Anything that isn't a 404 rethrows: "we couldn't load your inbox" is not "you don't have one".The two lists load with
allSettled, notall. Your own library is the page's reason to exist, so an inbox that 503s must not blank it — the tabs just don't appear, same as when the feature is off. The reverse isn't true, and isn't treated as such.Smaller calls
A contrast defect caught by verifying
I first built the tabs as an underline in
primary-accessible. That's a fixed brand colour with no dark variant, so a 2px#0033A0rule on the#101828dark surface is nearly invisible — and it passed every unit test. Switched to the filled pill thatmodel-catalog.page.htmland this page's own view toggle already use: 10.6:1 on the active pill, and one fewer idiom in the app.Browser-verified against the compiled stylesheet at 1080px in both themes — tabs, received-card variant, no horizontal overflow.
Tests
2341 passed, +17 from this change (12 tab-behaviour, 3 service contract, 2 mint-path).
tsc --noEmitclean.The one failure in a full run —
shared-view.page.spec.ts, a parallel-load timeout its own comment already documents — reproduces on clean develop with these changes stashed, so it's pre-existing and unrelated.To turn it on
Set
CDK_ARTIFACT_SHARE_INBOX_ENABLED=truefor the environment and deployplatform.yml. Existing dev shares predate the fan-out and won't appear until re-created; prod has no shares at all, so it's correct from its first one.🤖 Generated with Claude Code