Skip to content

feat: rename and retime a published page with stele amend - #5

Merged
ProJedi1234 merged 1 commit into
mainfrom
feat/amend-command
Aug 7, 2026
Merged

feat: rename and retime a published page with stele amend#5
ProJedi1234 merged 1 commit into
mainfrom
feat/amend-command

Conversation

@ProJedi1234

@ProJedi1234 ProJedi1234 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Follows stele-pages 570b375, which grew PATCH /pages/:slug — the verb that changes the two things about a page that used to be fixed at publication: where it lives and how long it lives.

$ stele amend q3-report --slug q3-final
https://stele.example.com/q3-final
kept until deleted

The shape

One command mapping 1:1 to the route, the way publish maps to POST and update to PUT:

stele amend <slug> [--slug <name>] [--ttl <days>]

Either flag alone, or both in one request — so a rename-and-retime cannot half-apply the way two commands over one route would. The alternative surfaces (separate rename/retime, or flags bolted onto update) were considered and rejected: update's "never changes the deadline" contract is worth keeping true.

The positional is <slug> and the rename flag is --slug, matching publish --slug. They collide in Swift, not on the command line — the option is declared .customLong("slug") against a newSlug property.

The trap this is mostly built around

An absent --ttl on amend means leave the deadline exactly where it is, where the same absence on publish means "take the server's default". PageTTL? is one optional with one absent case and the two verbs read it in opposite directions, so nothing enforces the difference except neither caller inventing a value to fill the silence with.

A ttl=7 sent because seven looked like a reasonable default would put a week's deadline on a page its author published to keep forever — and the 200 would look perfectly fine. So:

  • the query item is absent, not empty, when no --ttl was passed (a rename with no lifetime sends no ttl at all asserts the URL contains no ttl substring at all);
  • the smoke test publishes with --ttl never, renames with --slug only, and asserts expires is still null. That check is the only thing anywhere that would notice this bug.

Error advice became route-specific

SteleError.slugTaken grows an advice field, taken from the route's Expectation the way notFound's already was. It was the same mistake found twice: "omit --slug and let the server generate one" is the right next move on a publish and a false one on an amendment, where omitting it means do not rename and no slug is ever allocated. An agent following that advice would come back with a client-side "nothing to amend" and never reach the server.

Expectation.amend exists for the matching reason on the 404 side: write's advice names stele update and says to publish first, but an amendment's 404 is usually a page that has expired, where the tempting next move is --ttl never — which fails identically.

⚠️ slugTaken(String?)slugTaken(detail:advice:) is a source-breaking change to a public enum case. Fine on a 0.x line with one consumer, noted for the record.

Refusals

An amendment naming neither flag is refused before a credential is read or a byte is sent (exit 1). The server answers it with a 400 of its own, but it is a mistake in the invocation, and this side can name the two flags that would fix it — the same bargain PageIO.read strikes over an empty file. Taken name → exit 5, no live page (expired counts as none) → exit 7; both codes already existed.

Docs

The README's "A page's deadline is fixed when it is published … republish if you need a different lifetime" is now false and is rewritten: a deadline is fixed for a page's body, not for the page. PUT's 400 on ?ttl= stays, and stays correct — a replacement cannot retime a page.

A new section records what a rename costs, because it is the sharper half. The old name is freed the instant it commits, with no redirect and no tombstone, so a link already in circulation breaks — first as an ordinary 404, then, if somebody claims the name, as a link quietly pointing at their page. update keeps the URL and replaces the page; amend keeps the page and replaces the URL.

Also corrects a pre-existing claim that the server has no delete route — it has had one since stele-pages c83463a; what this CLI has is no command that reaches it.

Testing

0.2.00.3.0. 147 tests pass, build clean with no warnings, bash -n clean on the smoke script.

Smoke coverage (needs a live server, not run here): rename serves the bytes at the new name and 404s the old one at once; the freed name is claimable again; retime alone leaves the name alone; rename alone leaves a never deadline null; and the three refusals — the exit-1 one with the credential file moved aside, which is how the script proves the refusal happened before anything was sent.

Follow-up, not in this PR

The server's PublishSkill.swift — served at GET /skill and proxied verbatim by stele skill — currently states that "stele has no rename command and no retime command" and tells agents a lifetime is chosen once, at publication. True when 570b375 shipped; false the moment this merges. That's a companion PR in stele-pages.

🤖 Generated with Claude Code

https://claude.ai/code/session_018qEtLbpPoxtUtucznPohMG

Summary by CodeRabbit

  • New Features

    • Added page amendment support for renaming pages and changing deadlines without modifying content.
    • Added stele amend with validation, TTL options, and updated URL reporting.
    • Added client API support for amendments.
  • Bug Fixes

    • Improved slug-conflict and missing-page guidance with operation-specific remedies.
    • Clarified deadline behavior when TTL is omitted.
  • Documentation

    • Updated CLI, SDK, and integration documentation for amendment workflows.
  • Tests

    • Expanded coverage for renaming, deadline preservation, conflicts, validation, and missing pages.
  • Chores

    • Bumped the SDK version to 0.3.0.

Follows stele-pages 570b375, which grew `PATCH /pages/:slug` — the verb that
changes the two things about a page that used to be fixed at publication: where
it lives and how long it lives.

One command mapping 1:1 to the route, the way `publish` maps to POST and
`update` to PUT:

    stele amend <slug> [--slug <name>] [--ttl <days>]

Either flag alone, or both in one request — so a rename-and-retime cannot half
apply the way two commands over one route would.

The trap this change is mostly built around: an absent `--ttl` here means
*leave the deadline exactly where it is*, where the same absence on `publish`
means "take the server's default". `PageTTL?` is one optional with one absent
case and the two verbs read it in opposite directions, so nothing enforces the
difference except neither caller inventing a value to fill the silence with. A
`ttl=7` sent because seven looked reasonable would put a week's deadline on a
page published to be kept, and the 200 would look perfectly fine. Hence a test
asserting the parameter is *absent* rather than empty, and a smoke check that
renames a `--ttl never` page and watches its deadline stay null.

`SteleError.slugTaken` grows an `advice` field, taken from the route's
`Expectation` the way `notFound`'s already was. It was the same mistake found
twice: "omit `--slug` and let the server generate one" is the right next move on
a publish and a false one on an amendment, where omitting it means *do not
rename* and no slug is ever allocated — an agent following that advice would
come back with a client-side "nothing to amend" and never reach the server.
`Expectation.amend` exists for the same reason on the 404 side: `write`'s advice
names `stele update` and says to publish first, but an amendment's 404 is
usually a page that has *expired*, where the tempting next move is `--ttl never`
and it fails identically.

An amendment naming neither flag is refused before a credential is read or a
byte is sent — the server answers it with a 400, but it is a mistake in the
invocation, and this side can name the two flags that would fix it. Same bargain
`PageIO.read` strikes over an empty file.

Docs record what a rename costs, because it is the sharper half: the old name is
freed the instant it commits, with no redirect and no tombstone, so a link
already in circulation breaks — first as an ordinary 404, then, if somebody
claims the name, as a link pointing at their page. `update` keeps the URL and
replaces the page; `amend` keeps the page and replaces the URL.

0.2.0 -> 0.3.0. Also corrects a pre-existing claim that the server has no delete
route — it has had one since stele-pages c83463a; what this CLI has is no
command that reaches it.

147 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qEtLbpPoxtUtucznPohMG
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: da1287ca-72fa-4cc8-ae74-6443831d9f85

📥 Commits

Reviewing files that changed from the base of the PR and between 88a2671 and fa8ca8d.

📒 Files selected for processing (11)
  • README.md
  • Sources/SteleKit/Inputs.swift
  • Sources/SteleKit/Models.swift
  • Sources/SteleKit/SteleClient.swift
  • Sources/SteleKit/SteleError.swift
  • Sources/SteleKit/Version.swift
  • Sources/stele/Exit.swift
  • Sources/stele/PageCommands.swift
  • Sources/stele/Stele.swift
  • Tests/SteleKitTests/SteleClientTests.swift
  • scripts/integration-smoke.sh

📝 Walkthrough

Walkthrough

The PR adds page amendment support. Users can rename pages and change deadlines without changing content. The client sends PATCH requests, the CLI exposes stele amend, errors provide route-specific advice, and tests and documentation cover the new behavior.

Changes

Page amendment support

Layer / File(s) Summary
Client amendment API
Sources/SteleKit/Inputs.swift, Sources/SteleKit/Models.swift, Sources/SteleKit/SteleClient.swift, Sources/SteleKit/Version.swift
The client adds SteleClient.amend, shared slug query handling, PATCH requests without bodies, and updated TTL documentation. The version changes to 0.3.0.
Route-specific amendment errors
Sources/SteleKit/SteleError.swift
Slug conflicts now include caller-provided advice. Not-found and conflict messages distinguish publish and amend operations.
CLI amendment command
Sources/stele/PageCommands.swift, Sources/stele/Stele.swift, Sources/stele/Exit.swift
The CLI registers stele amend, validates options, parses TTL values, invokes the client API, reports the new URL, and updates exit-code guidance.
Amendment validation and documentation
Tests/SteleKitTests/SteleClientTests.swift, scripts/integration-smoke.sh, README.md
Tests cover request construction, error mapping, renaming, deadline behavior, content preservation, slug reuse, missing pages, and cleanup reporting. Documentation describes the command and amendment semantics.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AmendCommand
  participant SteleClient
  participant PagesAPI
  AmendCommand->>SteleClient: Submit slug and optional amendment values
  SteleClient->>PagesAPI: PATCH /pages/:slug with optional query parameters
  PagesAPI-->>SteleClient: Return PageLocation or route-specific error
  SteleClient-->>AmendCommand: Return result
  AmendCommand-->>AmendCommand: Report URL or actionable error
Loading

Poem

I hop through PATCH paths in the sun,
A renamed page keeps its content run.
Deadlines stay when TTL is bare,
New slugs bloom in fresh URL air.
Tests and docs now dance with care.


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/login.

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

@ProJedi1234
ProJedi1234 merged commit ab32d1d into main Aug 7, 2026
2 checks passed
@ProJedi1234
ProJedi1234 deleted the feat/amend-command branch August 7, 2026 10:08
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