Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 11 additions & 2 deletions .github/workflows/preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ jobs:
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
APP_WORKER_NAME: ${{ steps.names.outputs.worker_name }}
D1_DATABASE_NAME: ${{ steps.resources.outputs.d1_database_name }}
D1_DATABASE_ID: ${{ steps.resources.outputs.d1_database_id }}
run: |
set -euo pipefail
MOCK_API_TOKEN="$(openssl rand -hex 32)"
Expand All @@ -170,11 +172,18 @@ jobs:
service="$(basename "$dir")"
service_key="$(echo "$service" | tr '[:lower:]-' '[:upper:]_')"
mock_worker_name="${APP_WORKER_NAME}-mock-${service}"
mock_config="${dir}/wrangler-preview.generated.json"
deploy_log="deploy-mock-${service}.log"

bun tools/ci/sync-worker-secrets.ts --env "" --name "$mock_worker_name" --config "$dir/wrangler.jsonc" --set "MOCK_API_TOKEN=$MOCK_API_TOKEN"
bun tools/ci/mock-preview-config.ts \
--wrangler-config "$dir/wrangler.jsonc" \
--out-config "$mock_config" \
--d1-database-name "$D1_DATABASE_NAME" \
--d1-database-id "$D1_DATABASE_ID"

bun tools/ci/sync-worker-secrets.ts --env "" --name "$mock_worker_name" --config "$mock_config" --set "MOCK_API_TOKEN=$MOCK_API_TOKEN"

bunx wrangler deploy --env preview --name "$mock_worker_name" --config "$dir/wrangler.jsonc" 2>&1 | tee "$deploy_log"
bunx wrangler deploy --env preview --name "$mock_worker_name" --config "$mock_config" 2>&1 | tee "$deploy_log"

mock_url="$(node -e 'const fs = require("node:fs"); const text = fs.readFileSync(process.argv[1], "utf8").replace(/\u001b\[[0-9;]*m/g, ""); const worker = process.argv[2]; const escaped = worker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const workerUrlRegex = new RegExp(`https://(?:[a-zA-Z0-9-]+\\.)?${escaped}\\.[a-zA-Z0-9._-]+\\.workers\\.dev`, "g"); const matches = text.match(workerUrlRegex); process.stdout.write(matches?.at(-1) ?? "")' "$deploy_log" "$mock_worker_name")"
if [ -z "$mock_url" ]; then
Expand Down
346 changes: 309 additions & 37 deletions bun.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion client/agent-multi-select-combobox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import {
colors,
radius,
Expand Down
2 changes: 1 addition & 1 deletion client/app-session-refresh.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="bun" />
import { expect, mock, test } from 'bun:test'
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'

type QueueTask = Parameters<Handle['queueTask']>[0]

Expand Down
40 changes: 19 additions & 21 deletions client/app.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import { clientRoutes } from './routes/index.tsx'
import {
getPathname,
Expand Down Expand Up @@ -150,26 +150,24 @@ export function App(handle: Handle) {
) : null}
</nav>
<Router
setup={{
routes: clientRoutes,
fallback: (
<section>
<h2
css={{
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
marginBottom: spacing.sm,
color: colors.text,
}}
>
Not Found
</h2>
<p css={{ color: colors.textMuted }}>
That route does not exist.
</p>
</section>
),
}}
routes={clientRoutes}
fallback={
<section>
<h2
css={{
fontSize: typography.fontSize.lg,
fontWeight: typography.fontWeight.semibold,
marginBottom: spacing.sm,
color: colors.text,
}}
>
Not Found
</h2>
<p css={{ color: colors.textMuted }}>
That route does not exist.
</p>
</section>
}
/>
</main>
)
Expand Down
23 changes: 15 additions & 8 deletions client/client-router.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'

type RouterSetup = {
routes: Record<string, JSX.Element>
Expand Down Expand Up @@ -249,11 +249,18 @@ function ensureRouter() {
document.addEventListener('submit', handleDocumentSubmit)
}

export function listenToRouterNavigation(handle: Handle, listener: () => void) {
export function listenToRouterNavigation(
handle: Pick<Handle, 'signal'>,
listener: () => void,
) {
ensureRouter()
handle.on(routerEvents, {
navigate: () => listener(),
})
const onNavigate = () => listener()
routerEvents.addEventListener('navigate', onNavigate)
handle.signal.addEventListener(
'abort',
() => routerEvents.removeEventListener('navigate', onNavigate),
{ once: true },
)
}

export function getPathname() {
Expand Down Expand Up @@ -281,15 +288,15 @@ export function navigate(to: string) {
notify()
}

export function Router(handle: Handle, setup: RouterSetup) {
export function Router(handle: Handle<RouterSetup>) {
listenToRouterNavigation(handle, () => {
void handle.update()
})

return () => {
const path = getPathname()
const routeElement = matchRoute(path, setup.routes)
const routeElement = matchRoute(path, handle.props.routes)
if (routeElement) return routeElement
return setup.fallback ?? null
return handle.props.fallback ?? null
}
}
6 changes: 3 additions & 3 deletions client/counter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import {
colors,
radius,
Expand All @@ -11,8 +11,8 @@ type CounterSetup = {
initial?: number
}

export function Counter(handle: Handle, setup: CounterSetup = {}) {
let count = setup.initial ?? 0
export function Counter(handle: Handle<CounterSetup>) {
let count = handle.props.initial ?? 0

function increment() {
count += 1
Expand Down
2 changes: 1 addition & 1 deletion client/double-check.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'

type BlurHandler = (event: FocusEvent) => void
type ClickHandler = (event: MouseEvent) => void
Expand Down
7 changes: 4 additions & 3 deletions client/editable-text.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'

type EditableTextProps = {
id: string
Expand All @@ -20,7 +20,7 @@ const inheritTextStyles = {
color: 'inherit',
} as const

export function EditableText(handle: Handle) {
export function EditableText(handle: Handle<EditableTextProps>) {
let isEditing = false
let draftValue = ''
let isSaving = false
Expand All @@ -42,7 +42,8 @@ export function EditableText(handle: Handle) {
})
}

return (props: EditableTextProps) => {
return () => {
const props = handle.props
const buttonId = `${props.id}-button`

function startEditing() {
Expand Down
2 changes: 1 addition & 1 deletion client/entry.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createRoot } from 'remix/component'
import { createRoot } from 'remix/ui'
import { App } from './app.tsx'

const rootElement = document.getElementById('root') ?? document.body
Expand Down
2 changes: 1 addition & 1 deletion client/notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import {
colors,
radius,
Expand Down
111 changes: 111 additions & 0 deletions client/remix-ui-compat/jsx-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
css as cssMixin,
Fragment,
on as onMixin,
type Handle,
type Props as RemixProps,
type RemixElement,
type RemixNode,
} from 'remix/ui'
import {
jsx as remixJsx,
jsxDEV as remixJsxDev,
jsxs as remixJsxs,
} from 'remix/ui/jsx-runtime'

type LegacyEventHandler = {
bivarianceHack(event: any, signal: AbortSignal): void
}['bivarianceHack']
type LegacyEventMap = Record<string, LegacyEventHandler>
type RenderFn = () => RemixNode

type LegacyProps = Record<string, unknown> & {
children?: RemixNode
css?: Record<string, unknown>
key?: unknown
mix?: unknown
on?: LegacyEventMap
}
type KnownElementName =
| keyof HTMLElementTagNameMap
| keyof SVGElementTagNameMap
| keyof MathMLElementTagNameMap
type CompatIntrinsicElements = {
[elementName in KnownElementName]: RemixProps<
elementName & keyof globalThis.JSX.IntrinsicElements
> &
LegacyProps
}

type ElementType = string | ((handle: Handle<any, any>) => RenderFn)

function normalizeProps(props: Record<string, unknown> | null | undefined) {
if (!props) return {}
const { css, mix, on, ...rest } = props
const nextMix = []
if (css && typeof css === 'object') {
nextMix.push(cssMixin(css as any))
}
if (on && typeof on === 'object') {
for (const [eventName, handler] of Object.entries(on as LegacyEventMap)) {
nextMix.push(onMixin(eventName as never, handler as never))
}
}
if (Array.isArray(mix)) {
nextMix.push(...mix)
} else if (mix) {
nextMix.push(mix)
}
if (nextMix.length === 0) {
return props
}
return {
...rest,
mix: nextMix,
}
}

export function jsx(
type: ElementType,
props: Record<string, unknown> | null | undefined,
key?: string,
) {
return remixJsx(type, normalizeProps(props), key)
}

export function jsxs(
type: ElementType,
props: Record<string, unknown> | null | undefined,
key?: string,
) {
return remixJsxs(type, normalizeProps(props), key)
}

export function jsxDEV(
type: ElementType,
props: Record<string, unknown> | null | undefined,
key?: string,
) {
return remixJsxDev(type, normalizeProps(props), key)
}

export { Fragment }

export namespace JSX {
export type Element = RemixElement
export type ElementType = string | ((handle: Handle<any, any>) => RenderFn)
export type ElementChildrenAttribute = {
children: unknown
}
export interface IntrinsicAttributes {
key?: unknown
}
export type LibraryManagedAttributes<component, props> = component extends (
handle: Handle<infer componentProps, any>,
) => RenderFn
? componentProps extends Record<string, never>
? { key?: unknown }
: componentProps & { key?: unknown }
: props
export type IntrinsicElements = CompatIntrinsicElements
}
2 changes: 1 addition & 1 deletion client/routes/account.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import { isAdminEmail } from '#shared/admin.ts'
import { colors, spacing, typography } from '#client/styles/tokens.ts'

Expand Down
2 changes: 1 addition & 1 deletion client/routes/admin-agents.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import { createNotifications } from '#client/notifications.tsx'
import {
colors,
Expand Down
33 changes: 16 additions & 17 deletions client/routes/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Handle } from 'remix/component'
import { type Handle } from 'remix/ui'
import { AgentMultiSelectCombobox } from '#client/agent-multi-select-combobox.tsx'
import { ChatClient, type ChatClientSnapshot } from '#client/chat-client.ts'
import { navigate, routerEvents } from '#client/client-router.tsx'
Expand Down Expand Up @@ -1094,11 +1094,15 @@ export function ChatRoute(handle: Handle) {
}
}

handle.on(routerEvents, {
navigate: () => {
void syncActiveThreadFromLocation()
},
})
const handleRouterNavigate = () => {
void syncActiveThreadFromLocation()
}
routerEvents.addEventListener('navigate', handleRouterNavigate)
handle.signal.addEventListener(
'abort',
() => routerEvents.removeEventListener('navigate', handleRouterNavigate),
{ once: true },
)

handle.queueTask(() => {
if (typeof window === 'undefined') return
Expand Down Expand Up @@ -1464,11 +1468,7 @@ export function ChatRoute(handle: Handle) {
key={thread.id}
css={{
position: 'relative',
'&:hover [data-thread-delete-button="true"], &:focus-within [data-thread-delete-button="true"]':
{
opacity: 1,
pointerEvents: 'auto',
},
isolation: 'isolate',
}}
>
<button
Expand All @@ -1477,6 +1477,8 @@ export function ChatRoute(handle: Handle) {
click: () => navigate(buildThreadHref(thread.id)),
}}
css={{
position: 'relative',
zIndex: 0,
display: 'grid',
gap: spacing.xs,
width: '100%',
Expand Down Expand Up @@ -1554,6 +1556,7 @@ export function ChatRoute(handle: Handle) {
}
css={{
position: 'absolute',
zIndex: 1,
right: spacing.sm,
bottom: spacing.sm,
display: 'inline-flex',
Expand All @@ -1579,13 +1582,9 @@ export function ChatRoute(handle: Handle) {
? colors.onDanger
: colors.textMuted,
cursor: 'pointer',
opacity: 0,
pointerEvents: 'none',
opacity: 1,
pointerEvents: 'auto',
transition: `opacity ${transitions.normal}, background-color ${transitions.normal}, border-color ${transitions.normal}, color ${transitions.normal}`,
[mq.mobile]: {
opacity: 1,
pointerEvents: 'auto',
},
'&:hover': {
backgroundColor: colors.danger,
borderColor: colors.dangerHover,
Expand Down
Loading
Loading