mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-06 10:48:12 +02:00
feat: add readable Storybook branch bookmarks (#13231)
## Thinking Path > - Paperclip helps people manage AI agents for work. > - Maintainers use Storybook previews to review the board UI. > - Branch previews need stable bookmarks that people can read. > - The current publisher only provides a hashed branch path. > - This pull request adds a readable branch bookmark after each successful upload. > - Existing branch and build links keep working. ## Linked Issues or Issue Description Refs #13226. **What existing behavior does this improve?** Manual Storybook publication for repository branches. **Current behavior** The stable branch path contains a hash. The expected `/storybook/branches/master/` URL does not exist. **Proposed behavior** Each publication updates a readable bookmark. The action summary and Markdown artifact link it. Master uses `/storybook/branches/master/`. Other names use a safe path segment that preserves case and escapes special characters. **Reason and benefit** Maintainers can save and share a readable URL that opens the latest published branch build. **Breaking changes** None. Existing hashed branch entries still update. Existing build URLs remain valid. **Additional context** This follows the publisher in #13226. A duplicate search found no related bookmark change. It does not overlap planned core work in ROADMAP.md. ## What Changed - Generate readable branch bookmarks without collisions with existing build directories. - Upload the bookmark only after the full build and compatibility entry uploads succeed. - Link the bookmark in the existing summary and Markdown artifact. - Document branch-name escaping and test path isolation, stable links, and upload order. ## Verification - `node --test scripts/__tests__/storybook-deploy.test.mjs`: 20 tests pass. - `actionlint .github/workflows/storybook-deploy.yml .github/workflows/storybook-visual.yml`: passes. - `git diff --check`: passes. - [Master bookmark publication](https://github.com/paperclipai/paperclip/actions/runs/34613344758): passed. Opened `/storybook/branches/master/` in the browser and confirmed a story renders. Downloaded the Markdown report and verified its bookmark link. - [Feature branch bookmark publication](https://github.com/paperclipai/paperclip/actions/runs/34613449034): passed. Its separate bookmark uses `codex~2Fstorybook-bookmarks`. - Greptile: 5/5 on `dccaf10413ecf447cb34e622b6b3c505791abb51`, with no unresolved review threads. All current-head Paperclip CI gates pass, including typecheck, tests, build, browser suites, and the canary dry run. - Full local repository checks were not repeated for this focused publisher change. The preceding run passed typecheck but encountered unrelated native-session test failures. ## Risks - Special characters in branch names use `~HH` byte escapes. For example, `feature/foo` becomes `feature~2Ffoo`. - Names that could overlap an existing hashed build directory escape the final hyphen. Very long names retain a hash suffix. - The two branch entries update separately. If the final upload fails, the workflow fails and a rerun can repair the bookmark. ## Model Used OpenAI GPT-6 via Codex, with reasoning, shell tools, and live deployment verification. The exact runtime model ID and context-window size are not exposed in this session. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
3b03c4b9eb
commit
96bba78fba
5 files changed
+54
-7
No files matched your search
@@ -106,7 +106,23 @@ test('different branches have distinct stable URLs, including names that sanitiz
|
||||
const branches = ['feature/foo', 'feature-foo', 'Feature/foo', 'master', 'feature_foo', 'a'.repeat(100), 'a'.repeat(101)];
|
||||
const urls = branches.map(branch => storybookDestination({ ...input, branch }).url);
|
||||
assert.equal(new Set(urls).size, branches.length);
|
||||
assert.ok(urls.every(url => /^https:\/\/example.cloudfront.net\/storybook\/branches\/[a-z0-9-]+\/index.html$/.test(url)));
|
||||
assert.ok(urls.every(url => /^https:\/\/example.cloudfront.net\/storybook\/branches\/[A-Za-z0-9_~\-]+\/$/.test(url)));
|
||||
assert.equal(storybookDestination({ ...input, branch: 'master' }).url,
|
||||
'https://example.cloudfront.net/storybook/branches/master/');
|
||||
assert.equal(storybookDestination(input).url,
|
||||
'https://example.cloudfront.net/storybook/branches/feature~2Ffoo/');
|
||||
});
|
||||
test('bookmark paths cannot collide with other branches or existing immutable build directories', () => {
|
||||
const branches = ['feature/foo', 'feature~2Ffoo', '../master', 'master/index.html',
|
||||
'master', storybookDestination({ ...input, branch: 'master' }).branchKey,
|
||||
'a'.repeat(1000), 'a'.repeat(1001), 'café', 'caf~C3~A9'];
|
||||
const destinations = branches.map(branch => storybookDestination({ ...input, branch }));
|
||||
assert.equal(new Set(destinations.map(d => d.url)).size, branches.length);
|
||||
for (const d of destinations) {
|
||||
assert.doesNotMatch(d.bookmarkPrefix.slice('storybook/branches/'.length), /[/.]/);
|
||||
assert.ok(Buffer.byteLength(`${d.bookmarkPrefix}/index.html`) <= 1024);
|
||||
assert.ok(destinations.every(other => d.bookmarkPrefix !== other.prefix));
|
||||
}
|
||||
});
|
||||
test('redeploying a branch preserves its entry URL and creates a new build URL', () => {
|
||||
const a = storybookDestination(input);
|
||||
@@ -166,10 +182,12 @@ function publishFixture(options = {}) {
|
||||
test('publisher uploads a complete build then updates only that branch entry', () => {
|
||||
const { result, uploads } = publishFixture();
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
assert.equal(uploads.length, 2);
|
||||
assert.equal(uploads.length, 3);
|
||||
const d = storybookDestination(input);
|
||||
assert.ok(uploads[0].includes(`s3://${input.bucket}/${d.buildPrefix}/`));
|
||||
assert.ok(uploads[1].includes(`s3://${input.bucket}/${d.prefix}/index.html`));
|
||||
assert.ok(uploads[2].includes(`s3://${input.bucket}/${d.bookmarkPrefix}/index.html`));
|
||||
assert.ok(uploads[2].includes('no-cache,max-age=0,must-revalidate'));
|
||||
assert.ok(uploads[0].includes('--no-follow-symlinks'));
|
||||
assert.doesNotMatch(JSON.stringify(uploads), /--delete/);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user