Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5b39f2a
Add core SSR pipeline: renderToStream document, app-root clientEntry,…
cursoragent Jul 4, 2026
cbd90fe
Convert account, admin, and auth routes to SSR with shared loaders an…
cursoragent Jul 4, 2026
d8c9948
Add SSR perf layer (public data cache, per-request auth dedup, versio…
cursoragent Jul 4, 2026
2a5ef2d
Fix same-origin fetches in account secrets (placeholder-origin URLs b…
cursoragent Jul 4, 2026
03d69a4
Fix oxfmt formatting on ssr stub and admin-rbac strict-mode locator (…
cursoragent Jul 4, 2026
b7ecf6e
Bugbot fixes: scope SSR notFound to the rendered URL and silently rev…
cursoragent Jul 4, 2026
df85471
Address review feedback: gate hydration session refresh, guard-before…
cursoragent Jul 4, 2026
11b6160
Fix token selection navigation to notify the router and guard communi…
cursoragent Jul 4, 2026
bbd116e
Render community grid whenever listings are ready to avoid blank grid…
cursoragent Jul 4, 2026
8191cb0
Fix stale session cookie redirect loop
cursoragent Jul 4, 2026
54f1338
Show loading state when route URL diverges from loaded data to avoid …
cursoragent Jul 4, 2026
43f2238
Sync lastLoadedHref before navigate to avoid redundant token list ref…
cursoragent Jul 4, 2026
8648468
Embed OAuth authorize info in SSR to fix loading state
cursoragent Jul 4, 2026
52530dd
Fix OAuth authorize embed consumption on first render
cursoragent Jul 4, 2026
6eddb97
Spike: Remix Frame SSR + reload inside hydrated app stack
cursoragent Jul 4, 2026
abce83f
Generalize Remix frame plumbing with registry and guardrails
cursoragent Jul 4, 2026
b111ebd
Extend frame registry to match parameterized route patterns
cursoragent Jul 4, 2026
df87bbd
Convert community index grid to a named frame
cursoragent Jul 4, 2026
753805c
Convert community detail data region to a named frame
cursoragent Jul 4, 2026
ef72429
Add E2E coverage for community frame revalidation
cursoragent Jul 4, 2026
e8c5dc4
Make community detail frame E2E resilient to accumulated D1 state
cursoragent Jul 4, 2026
9b05ff8
Remove frame-spike scratch route now that community frames are the ex…
cursoragent Jul 4, 2026
2509562
Refresh session after username save so the nav updates immediately
cursoragent Jul 4, 2026
7f4c310
Only apply immutable asset caching when URLs are versioned by a real …
cursoragent Jul 4, 2026
1b9a5da
Pass through 304 asset revalidations instead of falling through to th…
cursoragent Jul 4, 2026
23e7756
Retry E2E D1 commands on SQLITE_BUSY lock contention
cursoragent Jul 4, 2026
653dd4e
Let the lint rule carry the frame-src enforcement instead of docs pro…
cursoragent Jul 4, 2026
b9945fd
Merge origin/main into cursor/remix-ssr-hydration-c537
cursoragent Jul 4, 2026
d932b1d
Merge branch 'cursor/remix-ssr-hydration-c537' into cursor/remix-fram…
cursoragent Jul 4, 2026
05d6c33
Guard detail shell against stale render and refetch loops
cursoragent Jul 4, 2026
6e7f63f
Dedupe in-flight shell fetches and revalidate shell with the frame
cursoragent Jul 4, 2026
ee3efac
Remix frames: server-rendered fragments with reload-based revalidatio…
kentcdodds Jul 4, 2026
d570a9c
Address Bugbot feedback: URL-driven token selection, dedupe loader da…
cursoragent Jul 4, 2026
66b383b
Merge branch 'cursor/remix-frames-revalidation-c537' into cursor/remi…
cursoragent Jul 4, 2026
cdc2d9e
Merge remote-tracking branch 'origin/main' into cursor/remix-ssr-hydr…
cursoragent Jul 4, 2026
53915ea
Guard admin route loads against duplicate in-flight fetches and error…
cursoragent Jul 4, 2026
292cd61
Show loading shell immediately on listing change and gate shell rende…
cursoragent Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions e2e/admin-rbac.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,25 @@ test('admin RBAC controls access, role assignment, and privacy boundaries', asyn
await expect(
page.getByRole('link', { name: 'Admin', exact: true }),
).toBeVisible()

await page.evaluate(() => {
;(window as Window & { __e2eMarker?: boolean }).__e2eMarker = true
})
await page.getByRole('link', { name: 'Admin', exact: true }).click()
await expect(page).toHaveURL(/\/admin\/users\/?$/)
await expect(page.getByRole('heading', { name: 'Admin users' })).toBeVisible()
// The first user is auto-selected, so the email can render in both the
// list and the detail panel — assert on the list entry specifically.
await expect(
page.getByRole('button', { name: adminUser.username }),
).toBeVisible()
await expect(page.getByText('Unable to load admin users.')).toHaveCount(0)
expect(
await page.evaluate(
() => (window as Window & { __e2eMarker?: boolean }).__e2eMarker,
),
).toBe(true)

await page.goto('/admin/users?pageSize=100')
await expect(page.getByRole('heading', { name: 'Admin users' })).toBeVisible()
await expect(page.getByText(memberUser.email)).toBeVisible()
Expand Down
52 changes: 52 additions & 0 deletions e2e/d1-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { spawnSync } from 'node:child_process'
import path from 'node:path'
import { createPasswordHash } from '@kody-internal/shared/password-hash.ts'
import { createStableUserIdFromEmail } from '../packages/worker/src/user-id.ts'

const projectRoot = path.resolve(import.meta.dirname, '..')

Expand Down Expand Up @@ -82,3 +83,54 @@ FROM users u, roles r
WHERE u.email = ${quoteSql(email)} AND r.name = ${quoteSql(role)};`.trim()
executeE2eD1Command(sql)
}

export async function seedCommunityListingInE2eDatabase(input: {
listingId: string
ownerEmail: string
name: string
description: string
tags?: Array<string>
kodyId?: string
packageId?: string
sourceId?: string
}) {
const ownerUserId = await createStableUserIdFromEmail(input.ownerEmail)
const tagsJson = JSON.stringify(input.tags ?? [])
const kodyId = input.kodyId ?? input.listingId
const packageId = input.packageId ?? `pkg-${input.listingId}`
const sourceId = input.sourceId ?? `src-${input.listingId}`
const sql = `
INSERT INTO community_listings (
id,
owner_user_id,
package_id,
source_id,
kody_id,
name,
description,
tags_json,
license,
pinned_commit,
status
) VALUES (
${quoteSql(input.listingId)},
${quoteSql(ownerUserId)},
${quoteSql(packageId)},
${quoteSql(sourceId)},
${quoteSql(kodyId)},
${quoteSql(input.name)},
${quoteSql(input.description)},
${quoteSql(tagsJson)},
'MIT',
'abc1234567890abcdef1234567890abcdef12345678',
'active'
)
ON CONFLICT(id) DO UPDATE SET
owner_user_id = excluded.owner_user_id,
name = excluded.name,
description = excluded.description,
tags_json = excluded.tags_json,
status = excluded.status,
updated_at = CURRENT_TIMESTAMP;`.trim()
executeE2eD1Command(sql)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
9 changes: 9 additions & 0 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ test('smoke test covers shell, auth redirect, and login', async ({ page }) => {
page.getByRole('link', { name: 'Secrets', exact: true }),
).toBeVisible()

// SPA-navigate to secrets: the client refetch must hit the same origin
// (regression: absolute placeholder-origin URLs caused "Failed to fetch").
await page.getByRole('link', { name: 'Secrets', exact: true }).click()
await expect(page).toHaveURL(/\/account\/secrets$/)
await expect(
page.getByRole('heading', { name: 'Saved secrets', exact: true }),
).toBeVisible()
await expect(page.getByText('Failed to fetch')).not.toBeVisible()

await page.context().clearCookies()
await page.goto('/privacy')
await expect(page.getByRole('heading', { name: 'Privacy' })).toBeVisible()
Expand Down
108 changes: 108 additions & 0 deletions e2e/ssr-hydration.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { expect, test } from '@playwright/test'
import { ensurePrimaryUserExists, primaryTestUser } from './auth-test-user.ts'
import { seedCommunityListingInE2eDatabase } from './d1-utils.ts'

const alphaListing = {
listingId: 'e2e-ssr-listing-alpha',
name: '@kody/alpha-ssr-package',
description: 'Alpha SSR fixture package for hydration tests.',
tags: ['alpha', 'ssr'],
}

const betaListing = {
listingId: 'e2e-ssr-listing-beta',
name: '@kody/beta-ssr-package',
description: 'Beta SSR fixture package for hydration tests.',
tags: ['beta', 'ssr'],
}

test('SSR community HTML hydrates SPA navigation and client search', async ({
page,
request,
}) => {
await ensurePrimaryUserExists(request)
await seedCommunityListingInE2eDatabase({
...alphaListing,
ownerEmail: primaryTestUser.email,
})
await seedCommunityListingInE2eDatabase({
...betaListing,
ownerEmail: primaryTestUser.email,
})

const htmlResponse = await request.get('/community')
expect(htmlResponse.ok()).toBe(true)
const rawHtml = await htmlResponse.text()
expect(rawHtml).toContain(alphaListing.description)
expect(rawHtml).toContain(betaListing.name)
expect(rawHtml).not.toContain('Loading community packages')
expect(rawHtml).toContain('id="rmx-data"')
expect(rawHtml).toContain('"community"')

const filteredHtmlResponse = await request.get('/community?q=beta')
expect(filteredHtmlResponse.ok()).toBe(true)
const filteredHtml = await filteredHtmlResponse.text()
expect(filteredHtml).toContain(betaListing.description)
expect(filteredHtml).not.toContain(alphaListing.name)

await page.goto('/community')
await expect(page.getByText(alphaListing.description)).toBeVisible()
await expect(page.getByText(betaListing.description)).toBeVisible()

await page.evaluate(() => {
;(window as Window & { __e2eMarker?: boolean }).__e2eMarker = true
})

await page.getByRole('link', { name: alphaListing.name }).click()
await expect(page).toHaveURL(
new RegExp(`/community/${alphaListing.listingId}$`),
)
await expect(page.getByText(alphaListing.description)).toBeVisible()
expect(
await page.evaluate(
() => (window as Window & { __e2eMarker?: boolean }).__e2eMarker,
),
).toBe(true)

await page.getByRole('link', { name: 'Community', exact: true }).click()
await expect(page).toHaveURL(/\/community$/)
expect(
await page.evaluate(
() => (window as Window & { __e2eMarker?: boolean }).__e2eMarker,
),
).toBe(true)

await page
.getByPlaceholder('Search by name, description, or tags')
.fill('beta')
await page.getByRole('button', { name: 'Search' }).click()
await expect(page).toHaveURL(/\/community\?q=beta/)
await expect(page.getByText(betaListing.description)).toBeVisible()
await expect(page.getByRole('link', { name: alphaListing.name })).toHaveCount(
0,
)
expect(
await page.evaluate(
() => (window as Window & { __e2eMarker?: boolean }).__e2eMarker,
),
).toBe(true)

await page.goto('/community?q=beta')
await expect(
page.getByPlaceholder('Search by name, description, or tags'),
).toHaveValue('beta')
await expect(page.getByText(betaListing.description)).toBeVisible()
await expect(page.getByRole('link', { name: alphaListing.name })).toHaveCount(
0,
)

// A server-rendered 404 must not pin later SPA navigations to the
// not-found fallback.
const notFoundResponse = await page.goto('/definitely-not-a-page')
expect(notFoundResponse?.status()).toBe(404)
await expect(page.getByRole('heading', { name: 'Not Found' })).toBeVisible()
await page.getByRole('link', { name: 'Community', exact: true }).click()
await expect(page).toHaveURL(/\/community$/)
await expect(page.getByText(betaListing.description)).toBeVisible()
await expect(page.getByRole('heading', { name: 'Not Found' })).toHaveCount(0)
})
29 changes: 29 additions & 0 deletions packages/worker/client/app-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { clientEntry, type EntryComponent, type Handle } from 'remix/ui'
import { App } from './app.tsx'
import { RouterLocationProvider } from './router-location.tsx'
import { type SessionInfo } from './session.ts'
import { type AppLoaderData } from '#client/loader-data-types.ts'

export const APP_ROOT_ENTRY_ID = '/client-entry.js#AppRoot'

export type AppRootProps = {
url: string
session: SessionInfo | null
loaderData?: AppLoaderData
notFound?: boolean
}

export const AppRoot: EntryComponent<AppRootProps> = clientEntry(
APP_ROOT_ENTRY_ID,
function AppRoot(handle: Handle<AppRootProps>) {
return () => (
<RouterLocationProvider url={handle.props.url}>
<App
embeddedSession={handle.props.session}
loaderData={handle.props.loaderData}
notFound={handle.props.notFound === true}
/>
</RouterLocationProvider>
)
},
)
Loading