Skip to content

feat: support drawer registry packages - #230

Merged
bobakemamian merged 3 commits into
mainfrom
codex/drawer-packages
Jul 2, 2026
Merged

feat: support drawer registry packages#230
bobakemamian merged 3 commits into
mainfrom
codex/drawer-packages

Conversation

@bobakemamian

@bobakemamian bobakemamian commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • publish and install drawers as registry packages using the same @desk/name identity as buttons
  • install drawer member buttons and transitive button requires into the lockfile
  • include drawer/member package updates in status, update, and passive update paths
  • update CLI docs and add integration coverage for drawer package add/press/status/update

Validation

  • go test ./...
  • git diff --check

Notes

Summary by CodeRabbit

  • New Features
    • CLI commands and documentation now consistently describe installable “packages,” supporting both buttons and drawers.
    • Publishing and registry install now autodetect and handle drawer packages, including drawer version stamping and drawer package metadata.
    • Status/update now display drawer vs button labels appropriately for pinned, skipped, error, and update-available items.
  • Bug Fixes
    • Creation and publishing now prevent name collisions across button and drawer slugs.
    • Passive and content updates now correctly apply drawer updates (including member dependencies) while respecting local drawer edits.
  • Tests
    • Added coverage for passive updates, registry drawer installs, and drawer-related content update behavior.

@mintlify

mintlify Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Buttons 🟢 Ready View Preview Jul 2, 2026, 4:03 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@bobakemamian, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 16300cc2-3c57-449a-94eb-dd22b4fd3d30

📥 Commits

Reviewing files that changed from the base of the PR and between 12e30ee and 85ed2f4.

📒 Files selected for processing (8)
  • cmd/status.go
  • internal/store/http_source.go
  • internal/store/install.go
  • internal/store/publish.go
  • internal/store/publish_test.go
  • internal/store/source.go
  • internal/store/store_test.go
  • internal/updater/content.go
📝 Walkthrough

Walkthrough

This PR extends the CLI and registry model to support drawer packages alongside buttons. It adds drawer-aware creation checks, version/schema fields, source and install/publish handling by package kind, kind-aware update/status reporting, documentation updates, and tests covering drawer install, publish, and update flows.

Changes

Drawer package support

Layer / File(s) Summary
Name-collision guards between buttons and drawers
internal/button/service.go, internal/button/service_test.go, internal/drawer/service.go, internal/drawer/service_test.go
Button and drawer creation now reject names already used by the other kind; tests cover both directions.
Drawer schema/version field
internal/drawer/entity.go, internal/drawer/schema_embedded.json, docs/schemas/drawer.schema.json
Adds an optional Version field to Drawer and corresponding schema entries.
Local and HTTP source support for drawer kind
internal/store/source.go, internal/store/http_source.go
Bundle gains Kind; local and HTTP sources detect and fetch button.json or drawer.json, with generic package wording in errors.
Install pipeline for drawer packages
internal/store/install.go, internal/store/store_test.go
Install dispatches by kind, adds drawer fetch/write/dependency helpers, and tests drawer installs plus member button dependencies.
Publish pipeline for drawer packages
internal/store/publish.go, internal/store/http_publish.go, internal/store/publish_test.go
Publishing now carries bundle kind, auto-detects local button vs drawer bundles, stamps drawer versions, and preserves symlink/collision checks.
Content update reporting for drawer kind
internal/updater/content.go, internal/updater/types.go, internal/updater/content_test.go
Update reporting carries dependency kind, merges manifest/lockfile dependencies, and uses drawer-aware local modification checks with drawer-specific tests.
CLI wording and passive-update testability
cmd/add.go, cmd/install.go, cmd/publish.go, cmd/status.go, cmd/update.go, cmd/passive_update.go, cmd/passive_update_test.go
CLI help/output now uses package/kind wording; passive update skip behavior is swappable and covered by a drawer passive-update test.
Documentation updates and end-to-end integration test
docs/cli/*, docs/concepts/*, test/integration/ota_update_test.go
Docs reflect package/drawer terminology and a full integration test covers drawer publish, install, and update flows with member buttons.

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

Sequence Diagram(s)

sequenceDiagram
  participant CLI as buttons add/install/publish
  participant Store as store.InstallManifest/PublishToRegistry
  participant Source as LocalSource/HTTPSource
  participant FS as local package files

  CLI->>Store: request package operation
  Store->>Source: Resolve/Fetch bundle
  Source-->>Store: Bundle with Kind
  alt drawer bundle
    Store->>Store: installDrawerPackage / loadLocalDrawerBundle
    Store->>FS: write drawer.json and lock data
    Store->>Store: walk drawer steps for dependencies
  else button bundle
    Store->>Store: installButtonPackage / loadLocalButtonBundle
    Store->>FS: write button.json and lock data
  end
  Store-->>CLI: status / result output
Loading
sequenceDiagram
  participant Updater as CheckContent/Apply
  participant Lock as buttons-lock.json
  participant Registry as store.Resolve/Fetch
  participant FS as installed package files

  Updater->>Lock: merge manifest and lock dependencies
  loop each dependency
    Updater->>Registry: resolve latest package
    Registry-->>Updater: ref.Kind and version
    Updater->>FS: inspect local files for modifications
    alt local edits present
      Updater-->>Updater: mark skipped
    else newer version available
      Updater->>Registry: fetch updated bundle
      Updater->>FS: write updated content
      Updater->>Lock: update kind/version/hash
    end
  end
Loading

Possibly related PRs

  • autonoco/buttons#190: Shares the same install/source plumbing in internal/store, extending button-only installation to additional package kinds.
  • autonoco/buttons#214: Related internal/store/http_source.go changes for registry fetch/index behavior.
  • autonoco/buttons#217: Related internal/store/http_publish.go and cmd/publish.go registry publishing kind handling.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support for drawer registry packages.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/drawer-packages

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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: 5

🧹 Nitpick comments (4)
internal/store/http_source.go (1)

174-193: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Error messages still say "button" for package-kind-agnostic failures.

Lines 174, 177, 188, and 193 hardcode "button %q..." in error messages, but at this point in Fetch the kind (button vs. drawer) isn't yet known — these errors can now fire for drawer downloads too, producing misleading messages like button "my-drawer": download: .... Line 159 already switched to the generic "package" wording; these should follow suit.

💬 Proposed fix
-		return nil, fmt.Errorf("button %q: download: %w", name, err)
+		return nil, fmt.Errorf("package %q: download: %w", name, err)
 	}
 	if int64(len(tarball)) > maxArtifactBytes {
-		return nil, fmt.Errorf("button %q: artifact exceeds %d bytes", name, int64(maxArtifactBytes))
+		return nil, fmt.Errorf("package %q: artifact exceeds %d bytes", name, int64(maxArtifactBytes))
 	}
 	...
 	if want != "" && got != want {
-		return nil, fmt.Errorf("button %q@%s: content hash mismatch (registry %s, got %s)", name, version, want, got)
+		return nil, fmt.Errorf("package %q@%s: content hash mismatch (registry %s, got %s)", name, version, want, got)
 	}

 	files, err := untarGz(tarball)
 	if err != nil {
-		return nil, fmt.Errorf("button %q: %w", name, err)
+		return nil, fmt.Errorf("package %q: %w", name, err)
 	}
🤖 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 `@internal/store/http_source.go` around lines 174 - 193, Package-kind-agnostic
failures in Fetch still hardcode “button” in error text, which can mislabel
drawer downloads. Update the error formatting in the tarball download, size
check, hash mismatch, and untar paths inside Fetch to use the generic “package”
wording already used earlier, keeping the same contextual fields like name and
version while removing button-specific terminology from these shared failure
messages.
internal/store/source.go (2)

107-127: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Silent precedence when both button.json and drawer.json exist.

If a package directory contains both files, button.json silently wins and drawer.json is never even opened. Given this PR elsewhere adds explicit button/drawer name-collision guards, consider making this same ambiguity fail loudly here too (at least for defensive parity), though this is a dev-only local source and the scenario requires a malformed on-disk package.

🤖 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 `@internal/store/source.go` around lines 107 - 127, The Source refs scan in
internal/store/source.go currently gives silent precedence to button.json over
drawer.json in the source listing logic. Update the reference collection path in
the Source/refs enumeration so it detects when both files exist for the same
package directory and fails loudly or otherwise rejects the ambiguous package
instead of silently picking button.json; use the existing os.ReadFile checks
around button.json and drawer.json and the refs append logic as the place to
enforce this defensive parity.

156-178: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: directory-not-found error still says "button" for drawer package lookups.

The new branches correctly generalize the "no button.json or drawer.json" message (Line 178), but the earlier os.ReadDir(dir) failure message just above ("button %q not found in source") wasn't updated and will misleadingly say "button" even when resolving a drawer package.

✏️ Suggested wording fix (outside the shown diff range)
-		return nil, fmt.Errorf("button %q not found in source: %w", name, err)
+		return nil, fmt.Errorf("package %q not found in source: %w", name, err)
🤖 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 `@internal/store/source.go` around lines 156 - 178, The source lookup error
message in the package resolver still hardcodes “button” when the directory read
fails, which is misleading for drawer lookups. Update the `LocalSource`/bundle
resolution path around the `os.ReadDir(dir)` failure to use the actual package
kind or a neutral term derived from the lookup context, matching the existing
`button.json` and `drawer.json` branches in this function. Keep the rest of the
validation logic unchanged, including the version checks and the final “no
button.json or drawer.json” fallback.
internal/updater/content.go (1)

145-170: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated Kind/Name fallback logic.

reportsWithSourceError (Lines 150-157) and checkOneDependency (Lines 183-188) both independently implement "default Kind to button" and "default Name to localNameFromPackage". Consider extracting a small shared helper to avoid drift if a third kind is ever added.

♻️ Proposed refactor
+func defaultKindAndName(entry manifest.LockEntry, pkgName string) (kind, name string) {
+	kind = entry.Kind
+	if kind == "" {
+		kind = "button"
+	}
+	name = entry.InstalledName
+	if name == "" {
+		name = localNameFromPackage(pkgName)
+	}
+	return kind, name
+}

Then use it in both reportsWithSourceError and checkOneDependency.

Also applies to: 172-188

🤖 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 `@internal/updater/content.go` around lines 145 - 170, The fallback logic for
Kind and Name is duplicated between reportsWithSourceError and
checkOneDependency, which can drift if defaults change. Extract a small shared
helper that resolves the effective Kind (defaulting to "button") and Name
(defaulting to localNameFromPackage) from the dependency/lock entry, then call
that helper from both reportsWithSourceError and checkOneDependency so both
paths stay consistent.
🤖 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 `@cmd/status.go`:
- Around line 79-82: The summary message in status reporting is still hardcoded
as “Buttons: up to date” even though the item labels now use
packageKindLabel(b.Kind) and can represent drawers as well. Update the final
availability check in status output to use the same generalized wording as
cmd/update.go’s summary so it stays accurate regardless of package kind, and
keep the change aligned with the status logic around packageKindLabel and the
available counter.

In `@internal/store/install.go`:
- Around line 296-303: The writeDrawerBundle loop currently writes every bundle
file with the same mode, so code/helper artifacts lose executable permissions.
Update the file-install logic in writeDrawerBundle to choose permissions based
on the bundle file type: keep spec/history JSON files at 0600, but write
non-JSON code/helper files at 0700. Use the existing rel/data handling in the
bundle.Files iteration to distinguish JSON from executable artifacts before
calling os.WriteFile.

In `@internal/store/publish_test.go`:
- Around line 58-68: The drawer fixture in the publish test is using overly
permissive permissions for private data. Update the helper in publish_test.go
that creates the test drawer contents so directories created with os.MkdirAll
and the pressed subdirectory use 0700, and the JSON fixtures written with
os.WriteFile for drawer.json and the pressed/run1.json history/spec files use
0600; keep the AGENTS.md fixture as-is if it is not part of the private
JSON/data set. Use the existing setup block around the drawer fixture to make
these permission changes consistently.

In `@internal/store/publish.go`:
- Around line 156-164: The artifact publishing loop in publishDrawer currently
reads every non-directory entry via os.ReadFile, which will follow symlinks and
can escape the drawer root. Update the entries handling in publishDrawer to
mirror LocalSource behavior by checking each os.DirEntry for symlinks before
reading, and skip any symlinked entry while still keeping the existing directory
skip and file collection logic intact.
- Line 105: Remove the trailing period from the fmt.Errorf message in the
ambiguous package error path so it matches ST1005 style; update the error string
in the publish flow where slug ambiguity is reported to say the package is
ambiguous and to rename one, but without ending punctuation.

---

Nitpick comments:
In `@internal/store/http_source.go`:
- Around line 174-193: Package-kind-agnostic failures in Fetch still hardcode
“button” in error text, which can mislabel drawer downloads. Update the error
formatting in the tarball download, size check, hash mismatch, and untar paths
inside Fetch to use the generic “package” wording already used earlier, keeping
the same contextual fields like name and version while removing button-specific
terminology from these shared failure messages.

In `@internal/store/source.go`:
- Around line 107-127: The Source refs scan in internal/store/source.go
currently gives silent precedence to button.json over drawer.json in the source
listing logic. Update the reference collection path in the Source/refs
enumeration so it detects when both files exist for the same package directory
and fails loudly or otherwise rejects the ambiguous package instead of silently
picking button.json; use the existing os.ReadFile checks around button.json and
drawer.json and the refs append logic as the place to enforce this defensive
parity.
- Around line 156-178: The source lookup error message in the package resolver
still hardcodes “button” when the directory read fails, which is misleading for
drawer lookups. Update the `LocalSource`/bundle resolution path around the
`os.ReadDir(dir)` failure to use the actual package kind or a neutral term
derived from the lookup context, matching the existing `button.json` and
`drawer.json` branches in this function. Keep the rest of the validation logic
unchanged, including the version checks and the final “no button.json or
drawer.json” fallback.

In `@internal/updater/content.go`:
- Around line 145-170: The fallback logic for Kind and Name is duplicated
between reportsWithSourceError and checkOneDependency, which can drift if
defaults change. Extract a small shared helper that resolves the effective Kind
(defaulting to "button") and Name (defaulting to localNameFromPackage) from the
dependency/lock entry, then call that helper from both reportsWithSourceError
and checkOneDependency so both paths stay consistent.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 52fe94c1-96ed-4d61-bd37-91802b9ae6f7

📥 Commits

Reviewing files that changed from the base of the PR and between 6827d1a and e8442f8.

📒 Files selected for processing (34)
  • cmd/add.go
  • cmd/install.go
  • cmd/passive_update.go
  • cmd/passive_update_test.go
  • cmd/publish.go
  • cmd/status.go
  • cmd/update.go
  • docs/cli/buttons.md
  • docs/cli/buttons_add.md
  • docs/cli/buttons_install.md
  • docs/cli/buttons_publish.md
  • docs/cli/buttons_status.md
  • docs/cli/buttons_update.md
  • docs/concepts/drawer-json.mdx
  • docs/concepts/folder-structure.mdx
  • docs/concepts/registry.mdx
  • docs/schemas/drawer.schema.json
  • internal/button/service.go
  • internal/button/service_test.go
  • internal/drawer/entity.go
  • internal/drawer/schema_embedded.json
  • internal/drawer/service.go
  • internal/drawer/service_test.go
  • internal/store/http_publish.go
  • internal/store/http_source.go
  • internal/store/install.go
  • internal/store/publish.go
  • internal/store/publish_test.go
  • internal/store/source.go
  • internal/store/store_test.go
  • internal/updater/content.go
  • internal/updater/content_test.go
  • internal/updater/types.go
  • test/integration/ota_update_test.go

Comment thread cmd/status.go
Comment thread internal/store/install.go
Comment thread internal/store/publish_test.go Outdated
Comment thread internal/store/publish.go Outdated
Comment thread internal/store/publish.go
@bobakemamian
bobakemamian merged commit 9b33537 into main Jul 2, 2026
17 checks passed
@bobakemamian
bobakemamian deleted the codex/drawer-packages branch July 2, 2026 09:06
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