Skip to content

feat: attachment content types and the URL an attachment is embedded by - #8

Merged
ProJedi1234 merged 1 commit into
mainfrom
feat/attachment-content-types
Aug 25, 2026
Merged

feat: attachment content types and the URL an attachment is embedded by#8
ProJedi1234 merged 1 commit into
mainfrom
feat/attachment-content-types

Conversation

@ProJedi1234

@ProJedi1234 ProJedi1234 commented Aug 24, 2026

Copy link
Copy Markdown
Owner

The server stores attachments now, and two facts about them are the library's to hold: which types a file can go up as, and where the bytes end up once it has. This is the SteleKit half — no command yet, so nothing user-visible changes.

attachmentsByExtension is a second map rather than more rows in the first, because the two are read under opposite rules. An unknown extension on a page is a shrug that falls through to text/html — the case the tool is named for. On an upload the same shrug costs the upload: the bytes go up, the server validates them as UTF-8, and the 415 that comes back names a content type nobody chose. So attachmentInferred returns nil and the caller can refuse before spending the round trip. Sharing an entry point would have meant one of the two callers getting the other's answer, which is why inferred is untouched and still non-optional.

SVG is absent and should stay absent. It is the one image format that is also a document, the server does not accept it, and offering it here would turn a considered 415 into a type this tool appears to know.

An attachment goes up through publish, which is not an omission: on the server an attachment is a page whose body is bytes — same route, same namespace, and the verbs that already existed reach it unchanged. A second method here would invent a distinction the wire does not have. filename is the one parameter only an attachment may carry, and its ordinary failure is the silent one the Query enum exists for: ?flename= earns a 201 and a download named after its slug.

bytesURL(for:) is derived from the response rather than composed from credential.host, and that is the load-bearing decision here. The server builds its URLs from its own configured base, which need not be the address this client dialled — a deployment behind a proxy, or reached over a tailnet name, answers with the public one. Composing from the host we reached would hand back a URL that works from this machine and from nowhere else, pasted into a page that outlives the machine. So scheme, host and port come from the server's answer and only the path is ours. It requires both rather than assuming them: URLComponents parses "" and a bare path quite happily, and /static/quiet-cedar-otter looks enough like an answer to be printed.

Checks

swift test — 167 tests in 18 suites, all passing. swift build -c release clean, no warnings.

New coverage: the attachment extension table and its refusal to guess; that the two maps stay separate in both directions; that SVG is off the list; ?filename= on the wire, alone and alongside ?slug=/?ttl=; that no filename means no parameter; that a hostile filename stays one query value; and the four bytesURL cases including a non-absolute viewer URL.

🤖 Generated with Claude Code

The server stores attachments now, and two facts about them are the
library's to hold: which types a file can go up as, and where the bytes
end up once it has.

`attachmentsByExtension` is a second map rather than more rows in the
first, because the two are read under opposite rules. An unknown
extension on a page is a shrug that falls through to `text/html` — the
case the tool is named for. On an upload the same shrug costs the
upload: the bytes go up, the server validates them as UTF-8, and the 415
names a content type nobody chose. So `attachmentInferred` returns nil
and the command refuses before spending the round trip. Sharing an entry
point would have meant one of the two callers getting the other's
answer. SVG is absent and should stay absent — it is the one image
format that is also a document, the server does not accept it, and
offering it here would turn a considered 415 into a type this tool
appears to know.

An attachment goes up through `publish`, which is not an omission: on
the server it is a page whose body is bytes, same route, same namespace,
and a second method here would invent a distinction the wire does not
have. `filename` is the one parameter only an attachment may carry, and
its ordinary failure is the silent one the `Query` enum exists for —
`?flename=` earns a 201 and a download named after its slug.

`bytesURL(for:)` is derived from the response rather than composed from
`credential.host`, and that is the load-bearing decision. The server
builds URLs from its own configured base, which need not be the address
this client dialled; composing from the host we reached would hand back
a URL that works from this machine and nowhere else, pasted into a page
that outlives the machine. Scheme, host and port come from the server's
answer and only the path is ours. It requires both rather than assuming
them, because `URLComponents` parses `""` and a bare path quite happily
and `/static/quiet-cedar-otter` looks enough like an answer to be
printed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: df65f719-b424-4b74-be6c-e447c35d7e77

📥 Commits

Reviewing files that changed from the base of the PR and between 1e9b00e and 46bed5f.

📒 Files selected for processing (4)
  • Sources/SteleKit/Inputs.swift
  • Sources/SteleKit/SteleClient.swift
  • Tests/SteleKitTests/InputsTests.swift
  • Tests/SteleKitTests/SteleClientTests.swift

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

This change adds attachment MIME inference, binary attachment publishing with optional filenames, and byte URL construction under /static. Tests cover supported types, query encoding, path safety, server URL preservation, and invalid viewer URLs.

Changes

Attachment support

Layer / File(s) Summary
Attachment type inference
Sources/SteleKit/Inputs.swift, Tests/SteleKitTests/InputsTests.swift
ContentType maps supported image, video, and PDF extensions. Inference is case-insensitive and returns nil for unknown, compound, or SVG extensions.
Attachment publishing
Sources/SteleKit/SteleClient.swift, Tests/SteleKitTests/SteleClientTests.swift
publish accepts binary content and an optional filename. The request includes content type and safely encoded query parameters.
Attachment byte URL construction
Sources/SteleKit/SteleClient.swift, Tests/SteleKitTests/SteleClientTests.swift
bytesURL(for:) creates encoded /static URLs, preserves scheme, host, and port, and rejects invalid viewer URLs.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 46bed

This PR adds attachment content-type handling and derives attachment URLs from the server response; no actionable merge-blocking risk remains in the supplied evidence.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant SteleClient
  participant SteleServer
  Caller->>SteleClient: publish binary attachment with content type and filename
  SteleClient->>SteleServer: POST attachment content with query parameters
  SteleServer-->>SteleClient: publish response
Loading

Poem

A rabbit packs bytes in a burrow so neat,
With filenames tucked safe and MIME types complete.
/static paths hop where the attachments stream,
Unknown marks vanish from the inference dream.
Tests guard each query, each slug, and each gate.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/settings/billing.

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

@ProJedi1234
ProJedi1234 merged commit 834f428 into main Aug 25, 2026
2 checks passed
@ProJedi1234
ProJedi1234 deleted the feat/attachment-content-types branch August 25, 2026 21:26
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.

1 participant