Skip to content

Stop copy-prompt tooltip jitter and show existing forks - #1311

Merged
kody-bot merged 3 commits into
mainfrom
cursor/fix-onboarding-tooltip-fork-status-7c54
Aug 8, 2026
Merged

kody-bot merged 3 commits into
mainfrom
cursor/fix-onboarding-tooltip-fork-status-7c54

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Aug 8, 2026 •

Copy link
Copy Markdown
Owner

Summary

Onboarding Copy prompt tooltips were jittering between vertically stacked starter cards (e.g. @kody/github and @kody/cloudflare). Remix UI popovers flip into nearby buttons and steal hover, so those tips now use CSS hover/focus tooltips with pointer-events: none.

Signed-in viewers who already have a matching kody_id saved package or a fork of the listing now see Copy prompt / Installed (or Forked) instead of Install on onboarding, community search cards, and listing detail.

Changes

  • Replace remix/ui/popover on onboarding Copy prompt buttons with CSS tooltips that cannot become hover targets.
  • Overlay a per-request viewerInstall from saved packages (kody_id) and community_forks without putting viewer state in the public listing cache.
  • Show Installed / Forked badges on /community cards and listing detail, and prefill Copy prompt + agent prompt when a fork already exists.
  • Docs: usage and contributing community-package pages.

Tests

  • resolveViewerListingInstalls prefers matching kody_id, then listing forks.
  • Community index / onboarding featured loaders overlay viewer installs for signed-in users and omit them for anonymous visitors.
  • Frame HTML includes Installed badges for signed-in viewers.
System recap — extends existing primitives (medium risk)

Mode: recap · Base: main @ 1916ffea · Head: 1a2cdcde

Classification: extends — viewer-install overlay changes community listing payloads and UI; saved-package lookup gains batch kody_id/id helpers.

Primitives touched

Primitive Group Impact
app-ui surfaces extends — CSS copy-prompt tooltips; viewerInstall on onboarding/detail/search UI
community-listings assistant extends — per-request viewerInstall overlay from forks + matching kody_id
saved-packages assistant extends — batch listSavedPackagesByKodyIds / listSavedPackagesByIds

System map

Signed-in community browse loads public listings, then overlays the viewer's existing fork/install so onboarding and listing UI show Copy prompt instead of Install.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).

flowchart LR
	appUi["app-ui<br/>Browser app"]:::extended
	communityListings["community-listings<br/>Community package listings"]:::extended
	savedPackages["saved-packages<br/>Saved packages"]:::extended
	appUi -->|"onboarding + /community + detail Copy prompt"| communityListings
	communityListings -->|"viewerInstall from forks + kody_id"| savedPackages
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Change flow

sequenceDiagram
	participant UI as Onboarding / community UI
	participant Loader as community-data
	participant Forks as community_forks
	participant Pkgs as saved_packages
	UI->>Loader: load featured / index / detail
	Loader->>Forks: list forks for viewer + listing ids
	Loader->>Pkgs: list packages by kody_id / fork package ids
	Loader-->>UI: viewerInstall overlay (Copy prompt)
Loading

Before / after

Onboarding Copy prompt tooltip

- remix/ui popover (top-layer, can flip onto another card and steal hover)
+ CSS hover/focus tooltip with pointer-events: none

Signed-in listing surfaces

- Always show Install until the user clicks it in this session
+ If matching kody_id or listing fork exists: Copy prompt + Installed/Forked
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Community listings and detail pages now show whether a package is Installed or Forked.
    • Existing installations offer Copy prompt or setup guidance instead of duplicate installation.
    • Onboarding cards display existing install status and provide prompts immediately.
    • Anonymous visitors continue to see no viewer-specific install information.
  • Documentation

    • Updated community package and onboarding guidance for install statuses, copy prompts, and setup options.
  • Tests

    • Added coverage for authenticated, anonymous, installed, forked, and adaptation-required scenarios.

Replace remix/ui popovers on onboarding Copy prompt buttons with CSS
tooltips that do not steal pointer events between stacked cards. Overlay
viewerInstall when a signed-in user already has a matching kody_id or
listing fork so onboarding, community search, and listing detail show
Copy prompt / Installed (or Forked) instead of Install.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 762acf53-fabe-481a-9cce-5fa3dacf8391

📥 Commits

Reviewing files that changed from the base of the PR and between ea4fcc0 and 16b7d80.

📒 Files selected for processing (4)
  • packages/worker/src/app/community-data.node.test.ts
  • packages/worker/src/app/community-data.ts
  • packages/worker/src/community/repo.ts
  • packages/worker/src/community/viewer-install.node.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/worker/src/community/repo.ts
  • packages/worker/src/app/community-data.node.test.ts
  • packages/worker/src/app/community-data.ts
  • packages/worker/src/community/viewer-install.node.test.ts

📝 Walkthrough

Walkthrough

The PR resolves authenticated viewer install states for community listings, forks, and saved packages. It propagates these states through loaders and displays Installed, Forked, Copy prompt, and adaptation-required behavior across community and onboarding surfaces.

Changes

Viewer install overlays

Layer / File(s) Summary
Install contracts and prompts
packages/worker/universal/community-public-types.ts, packages/worker/universal/loader-data.ts, packages/worker/src/app/community-public.ts
Public listing and loader types now include viewerInstall. Prompt builders map installed and adaptation-required states.
Install resolution and batch lookups
packages/worker/src/community/viewer-install.ts, packages/worker/src/community/repo.ts, packages/worker/src/package-registry/repo.ts, packages/worker/src/community/viewer-install.node.test.ts
Saved packages and forks are resolved per listing. Repository queries deduplicate and chunk IDs. Tests cover direct matches, forks, adaptation, and unmatched listings.
Authenticated loader overlays
packages/worker/src/app/community-data.ts, packages/worker/src/app/community-data.node.test.ts, packages/worker/src/app/handlers/community.node.test.ts, packages/worker/src/app/handlers/onboarding.node.test.ts
Index, onboarding, and detail loaders add viewer install data for authenticated requests and fail closed on lookup errors.
Community detail and listing rendering
packages/worker/client/routes/community-detail.tsx, packages/worker/src/app/community-detail-content.tsx, packages/worker/src/app/community-listings-content.tsx, packages/worker/src/app/handlers/community-detail.tsx, packages/worker/src/app/handlers/community-detail.frame.node.test.ts, packages/worker/src/app/handlers/community.frame.node.test.ts
Detail and listing surfaces display Installed or Forked badges. Detail results support existing installations and their prompts.
Onboarding status and copy prompts
packages/worker/client/routes/onboarding-starter-card.tsx, packages/worker/client/routes/onboarding-diy-card.tsx, docs/contributing/community-packages.md, docs/use/community-packages.md
Onboarding cards use existing viewer install data and inline CSS tooltips. Documentation describes Installed, Forked, and Copy prompt states.

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

Sequence Diagram(s)

sequenceDiagram
  participant Viewer
  participant CommunityLoader
  participant InstallResolver
  participant PackageAndForkRepositories
  participant CommunitySurface
  Viewer->>CommunityLoader: Request community listings or details
  CommunityLoader->>InstallResolver: Resolve viewer install state
  InstallResolver->>PackageAndForkRepositories: Query saved packages and forks
  PackageAndForkRepositories-->>InstallResolver: Return matching records
  InstallResolver-->>CommunityLoader: Return Installed, Forked, or adaptation-required state
  CommunityLoader-->>CommunitySurface: Provide viewerInstall
  CommunitySurface-->>Viewer: Render badges, status, and Copy prompt
Loading

Possibly related issues

Possibly related PRs

  • kentcdodds/kody#1233 — Modifies the same community detail, listing, and onboarding components while primarily changing their presentation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes both primary changes: preventing tooltip jitter and showing existing forks.
Description check ✅ Passed The description covers the changes, testing, and system impact, while its opening summary provides the intent despite no separate Intent heading.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/fix-onboarding-tooltip-fork-status-7c54

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.

@kody-bot
kody-bot marked this pull request as ready for review August 8, 2026 06:05
@github-actions

github-actions Bot commented Aug 8, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-1311.kody-a99.workers.dev

Worker: kody-pr-1311
D1: kody-pr-1311-db
KV: kody-pr-1311-oauth-kv

Mocks:

Viewer-install overlay calls readAuthenticatedAppUser from public community
and onboarding loaders, which previously assumed COOKIE_SECRET in tests that
only exercised listing JSON.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@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: 1

🧹 Nitpick comments (6)
packages/worker/src/package-registry/repo.ts (1)

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

Consider sharing the binding-limit constant.

maxSqlBindingsPerChunk is also defined in packages/worker/src/community/repo.ts. Two copies can drift if the D1 binding limit assumption changes. Move the constant next to chunkArray in @kody-internal/shared and import it in both modules.

🤖 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/worker/src/package-registry/repo.ts` at line 6, Move
maxSqlBindingsPerChunk from package-registry/repo.ts and community/repo.ts into
`@kody-internal/shared` alongside chunkArray, export it there, and update both
repositories to import and use the shared constant while removing their local
definitions.
packages/worker/src/app/community-public.ts (1)

176-185: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting the shared prompt bodies.

buildExistingInstallPrompt duplicates the body of buildInstallSuccessPrompt (Line 166), and buildExistingAdaptPrompt duplicates the body of buildInstallAdaptPrompt (Line 173). Only the opening sentence differs. If the agent instructions change later, an author must edit four strings and can update only two. Extract the shared tail into a helper and pass the opening sentence.

♻️ Example extraction
+function installSetupSteps(targetName: string) {
+	return `Call package_get for it and read its README, then walk me through any remaining setup: create required secrets or OAuth connections, approve package secret access if prompted, and run a quick test to confirm it works.`
+}
+
 export function buildExistingInstallPrompt(input: { targetName: string }) {
-	return `I already have the community package "${input.targetName}" in my Kody account. Call package_get for it and read its README, then walk me through any remaining setup: create required secrets or OAuth connections, approve package secret access if prompted, and run a quick test to confirm it works.`
+	return `I already have the community package "${input.targetName}" in my Kody account. ${installSetupSteps(input.targetName)}`
 }
🤖 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/worker/src/app/community-public.ts` around lines 176 - 185, Extract
the shared instruction tails used by buildInstallSuccessPrompt and
buildExistingInstallPrompt into a helper that accepts the differing opening
sentence, and do the same for buildInstallAdaptPrompt and
buildExistingAdaptPrompt. Update all four builders to reuse those helpers while
preserving their current opening text and prompt behavior.
packages/worker/src/community/viewer-install.node.test.ts (1)

29-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a case for the newest-fork fallback.

Every fork group in this test contains a fork whose targetKodyId matches the listing kodyId. The matchingKodyFork ?? listingForks[0] fallback in resolveViewerListingInstalls is therefore never exercised. Add a listing with two forks that both use a renamed targetKodyId, then assert that the newest fork wins.

🤖 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/worker/src/community/viewer-install.node.test.ts` around lines 29 -
51, Extend the fixture data used by the viewer install test with a listing whose
two forks have renamed targetKodyId values, so neither matches the listing
kodyId. Add assertions covering resolveViewerListingInstalls to verify the
fallback selects the newest fork rather than the older one.
packages/worker/src/community/repo.ts (1)

738-756: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document that the returned order is per chunk only.

ORDER BY created_at ASC applies inside each chunk. When listingIds spans more than one chunk, the concatenated forks array is not globally sorted. The current consumer resolveViewerListingInstalls re-sorts each listing group, so behavior is correct today. Add a short doc comment so a future caller does not rely on a global order.

🤖 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/worker/src/community/repo.ts` around lines 738 - 756, The function
returning the concatenated forks must document that ordering is only guaranteed
within each SQL chunk, not globally across all listing IDs. Add a short doc
comment immediately before the relevant function or return flow around the
chunked query, noting that callers requiring global ordering must sort the
result themselves; do not change the query or consumer behavior.
packages/worker/src/app/community-data.ts (1)

300-355: 🚀 Performance & Scalability | 🔵 Trivial

Consider the added per-request database cost on the index page.

For a signed-in viewer, every community index request now runs three parallel D1 queries plus an optional fourth, with listingIds and kodyIds scaling to the limit cap of 100. The public listing rows still come from the data cache, but the overlay does not. Consider a short-lived per-user cache keyed by user id, or restricting the overlay to the listings actually rendered above the fold, if index traffic from signed-in users is high.

🤖 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/worker/src/app/community-data.ts` around lines 300 - 355, Reduce the
per-request database load in loadViewerListingInstalls by avoiding overlay
queries for every cached index listing. Prefer a short-lived per-user cache
keyed by userId for the resolved viewer installs; alternatively, limit
input.listings to the above-the-fold listings before building listingIds and
kodyIds, while preserving the existing install-resolution behavior.
packages/worker/src/app/community-data.node.test.ts (1)

124-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reset mocks in a shared hook and assert the scoped userId.

Two issues in this file:

  1. Only listSavedPackagesByKodyIds is reset, and only in two of the four tests. Every other mock keeps its value across tests, so the suite depends on execution order. Add a beforeEach that calls vi.resetAllMocks() and re-applies defaults, or enable clearMocks in the Vitest config.
  2. No test asserts that the repository lookups receive the signed-in viewer's userId. Per-user isolation is enforced only by the user_id filter in the real queries, which are mocked here. Assert the argument so a future refactor cannot drop the scoping.

As per coding guidelines: "Maintain complete per-user isolation: each signed-in user must have an independent assistant with separate packages, jobs, secrets, values, memories, remote connectors, email inboxes, and durable storage."

💚 Suggested additions
+beforeEach(() => {
+	vi.resetAllMocks()
+})
+
 test('community index overlays matching kody_id installs for signed-in viewers', async () => {
 	expect(data.listings[0]?.viewerInstall).toEqual({
 		status: 'installed',
 		targetName: '`@burhan/github`',
 		agentPrompt: buildExistingInstallPrompt({ targetName: '`@burhan/github`' }),
 	})
+	expect(mockModule.listSavedPackagesByKodyIds).toHaveBeenCalledWith(
+		undefined,
+		expect.objectContaining({ userId: 'viewer-1' }),
+	)
+	expect(mockModule.listCommunityForksByListingIdsAndUser).toHaveBeenCalledWith(
+		undefined,
+		expect.objectContaining({ userId: 'viewer-1' }),
+	)
 })

Also applies to: 189-203

🤖 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/worker/src/app/community-data.node.test.ts` around lines 124 - 133,
Update the test setup around the community index tests to add a shared
beforeEach that calls vi.resetAllMocks() and reapplies the required default mock
responses, removing the ad hoc partial resets. In the signed-in viewer tests,
assert that repository lookup mocks such as listSavedPackagesByKodyIds and
listCommunityForksByListingIdsAndUser receive the authenticated user’s userId,
preserving per-user isolation.

Source: Coding guidelines

🤖 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 `@packages/worker/src/app/community-data.ts`:
- Around line 102-109: Wrap the readAuthenticatedAppUser call in
loadCommunityIndexData with a handler-level try/catch; log any session parsing
or user lookup failure and continue with user set to null. Keep the existing
listings flow and pass the resolved anonymous user into
overlayViewerInstallsOnListings so the public community index still loads.

---

Nitpick comments:
In `@packages/worker/src/app/community-data.node.test.ts`:
- Around line 124-133: Update the test setup around the community index tests to
add a shared beforeEach that calls vi.resetAllMocks() and reapplies the required
default mock responses, removing the ad hoc partial resets. In the signed-in
viewer tests, assert that repository lookup mocks such as
listSavedPackagesByKodyIds and listCommunityForksByListingIdsAndUser receive the
authenticated user’s userId, preserving per-user isolation.

In `@packages/worker/src/app/community-data.ts`:
- Around line 300-355: Reduce the per-request database load in
loadViewerListingInstalls by avoiding overlay queries for every cached index
listing. Prefer a short-lived per-user cache keyed by userId for the resolved
viewer installs; alternatively, limit input.listings to the above-the-fold
listings before building listingIds and kodyIds, while preserving the existing
install-resolution behavior.

In `@packages/worker/src/app/community-public.ts`:
- Around line 176-185: Extract the shared instruction tails used by
buildInstallSuccessPrompt and buildExistingInstallPrompt into a helper that
accepts the differing opening sentence, and do the same for
buildInstallAdaptPrompt and buildExistingAdaptPrompt. Update all four builders
to reuse those helpers while preserving their current opening text and prompt
behavior.

In `@packages/worker/src/community/repo.ts`:
- Around line 738-756: The function returning the concatenated forks must
document that ordering is only guaranteed within each SQL chunk, not globally
across all listing IDs. Add a short doc comment immediately before the relevant
function or return flow around the chunked query, noting that callers requiring
global ordering must sort the result themselves; do not change the query or
consumer behavior.

In `@packages/worker/src/community/viewer-install.node.test.ts`:
- Around line 29-51: Extend the fixture data used by the viewer install test
with a listing whose two forks have renamed targetKodyId values, so neither
matches the listing kodyId. Add assertions covering resolveViewerListingInstalls
to verify the fallback selects the newest fork rather than the older one.

In `@packages/worker/src/package-registry/repo.ts`:
- Line 6: Move maxSqlBindingsPerChunk from package-registry/repo.ts and
community/repo.ts into `@kody-internal/shared` alongside chunkArray, export it
there, and update both repositories to import and use the shared constant while
removing their local definitions.
🪄 Autofix

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: 962ad244-9ceb-4749-ad4a-9408bcf67fb6

📥 Commits

Reviewing files that changed from the base of the PR and between e52578a and ea4fcc0.

📒 Files selected for processing (21)
  • docs/contributing/community-packages.md
  • docs/use/community-packages.md
  • packages/worker/client/routes/community-detail.tsx
  • packages/worker/client/routes/onboarding-diy-card.tsx
  • packages/worker/client/routes/onboarding-starter-card.tsx
  • packages/worker/src/app/community-data.node.test.ts
  • packages/worker/src/app/community-data.ts
  • packages/worker/src/app/community-detail-content.tsx
  • packages/worker/src/app/community-listings-content.tsx
  • packages/worker/src/app/community-public.ts
  • packages/worker/src/app/handlers/community-detail.frame.node.test.ts
  • packages/worker/src/app/handlers/community-detail.tsx
  • packages/worker/src/app/handlers/community.frame.node.test.ts
  • packages/worker/src/app/handlers/community.node.test.ts
  • packages/worker/src/app/handlers/onboarding.node.test.ts
  • packages/worker/src/community/repo.ts
  • packages/worker/src/community/viewer-install.node.test.ts
  • packages/worker/src/community/viewer-install.ts
  • packages/worker/src/package-registry/repo.ts
  • packages/worker/universal/community-public-types.ts
  • packages/worker/universal/loader-data.ts

Comment thread packages/worker/src/app/community-data.ts Outdated
Session parsing or user lookup errors on /community, onboarding featured
listings, and listing detail now degrade to anonymous listings instead of
failing the page. Overlay queries stay scoped to the signed-in user.

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
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.

3 participants