Skip to content

feat: show path and state metadata in the managed attachments comment#370

Merged
Zach Dunn (zachdunn) merged 8 commits into
mainfrom
claude/issue-365-attachment-metadata-4d50c3
Jul 22, 2026
Merged

feat: show path and state metadata in the managed attachments comment#370
Zach Dunn (zachdunn) merged 8 commits into
mainfrom
claude/issue-365-attachment-metadata-4d50c3

Conversation

@zachdunn

@zachdunn Zach Dunn (zachdunn) commented Jul 21, 2026

Copy link
Copy Markdown
Member

Closes #365.

In plain terms

A screenshot attached to a PR used to show up as settings-a3f9.webp. If it was uploaded with --state before on /settings, it now shows up as /settings · before underneath the image. Attachments without that metadata render exactly as they did before.

#361 added the canonical metadata vocabulary; the managed comment ignored all of it. This connects the two.

What it renders

Inline images get a <sub> caption; list rows and the collapsed overflow list get a · suffix after the filename.

### 📎 Attachments

<a href="…/before.webp"><img width="400" alt="before.webp" src="…"></a>
<sub>/settings · before</sub>

<a href="…/after.webp"><img width="400" alt="after.webp" src="…"></a>
<sub>/settings · after</sub>

<a href="…/hero.png"><img width="400" alt="hero.png" src="…"></a>

- [build.log](…) · /admin?a=1&amp;b=2 · error
- [notes.pdf](…)

The third shot has no metadata and renders byte-for-byte as it does on main today.

The cost question #365 raised

The issue flagged that this adds a D1 read to a path that runs on every webhook delivery, and asked for a rows-read estimate against #352's perf work before shipping. Measured against production D1 rather than estimated — running the real statement shape and reading rows_read off the response:

Query Keys Meta rows/key rows_read
Unfiltered (today's getMetadataForKeys) 2 6 16
Unfiltered 4 4 24
Filtered AND meta_key IN ('path','state') 2 6 (2 match) 8
Filtered, zero matches 4 4 (0 match) 12

So unfiltered ≈ 2·K + total_rows, filtered ≈ 3·K + matched. Two things follow:

  1. The filtered read is ~3–5 rows per attachment, and stays flat as the vocabulary grows, because it rides the (workspace, object_key, meta_key) primary-key index. Production attachment counts are 1–2 per PR at the median, 8 at the max, so a median delivery pays ~5–10 rows.
  2. The same delivery already pays more. autoPromoteAndComment runs promoteBranchAttachments before gatherAndUpsert, and that already does a full unfiltered getMetadataForKeys over the staged keys on every opened/reopened/synchronize. gatherGalleries is two more D1 queries. "No D1 on this path" was true of gatherAttachments in isolation, not of the sync path.

No perf redesign needed — just a key filter on the existing primitive.

Privacy

The comment posts publicly, on repos whose visibility uploads.sh does not check. device and software come from EXIF, so they must not end up there. The restriction lives at the query, not the renderer: the server never fetches them for this path, so a later renderer bug can't leak what was never read.

Reinforced twice more: the CLI's fallback narrows at its mapping site, and AttachmentItem.meta is typed as { path?: string; state?: string } rather than a metadata bag, so the narrow set is a compile-time property.

Degradation

The requirement was that a comment with no metadata look exactly like it does today. test/fixtures/github-comment-golden.json and github-comment-golden-cap.json were deliberately not regenerated — their items carry no meta, so correct code has to reproduce them byte-for-byte. Leaving them untouched turns them into the regression test. A new third fixture covers the metadata cases from both render paths.

Decisions worth flagging

  • path and state only. viewport/device/software are noise in a PR context; url wraps badly. The privacy argument independently points at the same narrow set.
  • Fixed key set, with a per-workspace off switch. githubCommentShowMetadata, default-on, mirroring githubCommentLinkToFilePage (Make managed-comment file-page linking configurable (per-workspace setting / in-repo config) #304) exactly — KV-backed, admin-API only, no web UI. Not a configurable key list.
  • The CLI gh fallback gets the same data via a new opt-in ?metadata=1 on GET /v1/:workspace/files, so the two render paths don't diverge and the golden fixture stays a real cross-check. It inherits Make managed-comment file-page linking configurable (per-workspace setting / in-repo config) #304's known caveat: the CLI has no WorkspaceRecord in scope, so a workspace that sets the flag false and falls through to gh still gets captions.
  • No truncation of long values — a wrapped caption beats a mangled one.
  • Grouping before/after pairs side by side is the obvious next win and is deliberately out of scope.

Not in scope

Re-rendering historical comments. New renders only.

Test plan

  • pnpm test — 173 files / 2194 tests
  • pnpm typecheck, pnpm lint clean
  • Both pre-existing golden fixtures pass without regeneration
  • Cross-workspace isolation verified by deliberate-failure check: pointing the metadata read at another workspace makes the new test fail with the intruder's caption rendered into the wrong comment
  • Post-deploy: confirm a real PR comment renders captions on an upload tagged with --state

Rollout

No D1 migration — the flag is KV, and the filter uses the existing index. Deploy order doesn't matter: metadata=1 is additive, and an older worker ignoring it degrades the CLI fallback to today's output.

Summary by CodeRabbit

  • New Features

    • File listings can now optionally include queryable metadata such as upload path and state.
    • Managed GitHub attachment comments can display path/state captions beneath images and alongside attachments, including collapsed items.
    • Added a workspace setting to enable or disable metadata captions.
    • Upload listing clients can request metadata hydration.
  • Bug Fixes

    • Metadata captions are safely escaped and omitted when unavailable.
    • Metadata remains scoped to the correct workspace and excludes unrelated fields.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
uploads-auth 2cb16d3 Commit Preview URL

Branch Preview URL
Jul 22 2026, 12:13 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 21, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
uploads-api 2cb16d3 Commit Preview URL

Branch Preview URL
Jul 22 2026, 12:12 AM

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (2)
  • coderabbit:review
  • review
🚫 Excluded labels (none allowed) (1)
  • wip

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8467b749-a6e3-442f-bb38-66102f850437

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-365-attachment-metadata-4d50c3

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/github-comment-render.ts`:
- Around line 138-141: Update escapeMarkdownText in
apps/api/src/github-comment-render.ts at lines 138-141 and the corresponding
renderer helper in packages/uploads/src/github.ts at lines 248-251 to include ~
in the escaped markdown character class. Add matching coverage for literal tilde
metadata values in both renderer test suites.

In `@packages/uploads/src/commands.ts`:
- Around line 684-700: Update the listAll call in the attachment item mapping to
request only the canonical metadata keys path and state through its
metadata-filtering option, and assert that filtered request in the associated
test path. Keep the existing mapping and rendering behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4fb648c8-8334-4074-ab5c-56634aa7c680

📥 Commits

Reviewing files that changed from the base of the PR and between 9306ba1 and 78b22ed.

📒 Files selected for processing (20)
  • .changeset/attachment-comment-metadata.md
  • apps/api/src/file-metadata.ts
  • apps/api/src/github-comment-render.test.ts
  • apps/api/src/github-comment-render.ts
  • apps/api/src/github-comment.test.ts
  • apps/api/src/github-comment.ts
  • apps/api/src/routes/admin-ui.test.ts
  • apps/api/src/routes/admin-ui.ts
  • apps/api/src/routes/files.ts
  • apps/api/src/workspace.ts
  • apps/api/test/file-metadata-sqlite.test.ts
  • apps/api/test/helpers/fake-file-metadata-table.ts
  • apps/api/test/routes-files.test.ts
  • packages/uploads/src/client.ts
  • packages/uploads/src/commands.ts
  • packages/uploads/src/github.ts
  • packages/uploads/test/client-metadata.test.ts
  • packages/uploads/test/commands-comment.test.ts
  • packages/uploads/test/github-render-golden.test.ts
  • test/fixtures/github-comment-golden-meta.json

Comment thread apps/api/src/github-comment-render.ts Outdated
Comment on lines +138 to +141
/** Backslash-escape the markdown metacharacters that can appear in a metadata value. */
function escapeMarkdownText(s: string): string {
return s.replace(/([\\`*_[\]])/g, "\\$1");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Escape ~ in literal markdown captions.

GitHub interprets ~~value~~ as strikethrough, so a valid printable metadata value is not rendered verbatim in list and overflow captions. Include ~ in the escaped character class and add the same coverage in both renderer test suites.

  • apps/api/src/github-comment-render.ts#L138-L141: escape ~ in escapeMarkdownText.
  • packages/uploads/src/github.ts#L248-L251: apply the identical escape change.
📍 Affects 2 files
  • apps/api/src/github-comment-render.ts#L138-L141 (this comment)
  • packages/uploads/src/github.ts#L248-L251
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/github-comment-render.ts` around lines 138 - 141, Update
escapeMarkdownText in apps/api/src/github-comment-render.ts at lines 138-141 and
the corresponding renderer helper in packages/uploads/src/github.ts at lines
248-251 to include ~ in the escaped markdown character class. Add matching
coverage for literal tilde metadata values in both renderer test suites.

Comment on lines +684 to +700
const items: AttachmentItem[] = (
await client.listAll({ prefix: ghKeyPrefix(target), metadata: true })
).map(({ key, url, embedUrl, pageUrl, metadata }) => {
// The list endpoint returns every metadata key; the comment renders only
// these two. Narrowing here keeps both render paths byte-identical.
const path = metadata?.path;
const state = metadata?.state;
return {
key,
url,
embedUrl,
pageUrl,
...(path || state
? { meta: { ...(path ? { path } : {}), ...(state ? { state } : {}) } }
: {}),
};
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Request only canonical metadata keys in the CLI fallback.

Line 685 fetches every metadata field, including fields such as device and software, before dropping them locally. This violates the requirement to enforce metadata privacy at query time. Extend the listing contract to request only path and state, and assert that filtered request in this test path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/uploads/src/commands.ts` around lines 684 - 700, Update the listAll
call in the attachment item mapping to request only the canonical metadata keys
path and state through its metadata-filtering option, and assert that filtered
request in the associated test path. Keep the existing mapping and rendering
behavior unchanged.

GitHub's strikethrough extension treats a matching pair of one or two
tildes as markup, so a metadata value like /a~b~c rendered with b struck
through in the markdown list-suffix context. Same class as the * and _
already escaped. Both renderer copies and both test suites.
@zachdunn
Zach Dunn (zachdunn) merged commit c553499 into main Jul 22, 2026
5 checks passed
@zachdunn
Zach Dunn (zachdunn) deleted the claude/issue-365-attachment-metadata-4d50c3 branch July 22, 2026 00:22
@uploads-sh

uploads-sh Bot commented Jul 22, 2026

Copy link
Copy Markdown

📎 Attachments

smoke-after.webp
/settings · after

smoke-before.webp
/settings · before

Maintained by uploads.sh — re-uploading a file with the same name updates it everywhere it is embedded.
Add media: uploads put <file> --pr <N> --comment (or --issue <N>) · docs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface canonical upload metadata in the managed attachments comment

1 participant