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
2 changes: 1 addition & 1 deletion .claude/rules/global.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const filtered = filterUndefined(obj)
```

## Deployment flags in the browser
Client code inside a workspace reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never the `isHosted`/`isBillingEnabled` constants from `env-flags`. Those constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit, so a tab recovered from one would render Sim Cloud as self-hosted. The reader is seeded from the server-resolved workspace host context. Server code keeps reading `env-flags`.
Client code inside a workspace, organization, or standalone settings surface reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never the `isHosted`/`isBillingEnabled` constants from `env-flags`. Those constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit, so a tab recovered from one would render Sim Cloud as self-hosted. The reader is seeded from the server-resolved workspace host context, organization layout, or standalone settings layout. Server code keeps reading `env-flags`.

## Package Manager
Use `bun` and `bunx`, not `npm` and `npx`.
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/global.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const filtered = filterUndefined(obj)
```

## Deployment flags in the browser
Client code inside a workspace reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never the `isHosted`/`isBillingEnabled` constants from `env-flags`. Those constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit, so a tab recovered from one would render Sim Cloud as self-hosted. The reader is seeded from the server-resolved workspace host context. Server code keeps reading `env-flags`.
Client code inside a workspace, organization, or standalone settings surface reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never the `isHosted`/`isBillingEnabled` constants from `env-flags`. Those constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit, so a tab recovered from one would render Sim Cloud as self-hosted. The reader is seeded from the server-resolved workspace host context, organization layout, or standalone settings layout. Server code keeps reading `env-flags`.

## Package Manager
Use `bun` and `bunx`, not `npm` and `npx`.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ You are a professional software engineer. All code must follow best practices: a
- `omit(obj, keys)` / `filterUndefined(obj)` from `@sim/utils/object` — object trimming; never `Object.fromEntries(Object.entries(...).filter(...))`
- `truncate(str, maxLength, suffix?)` from `@sim/utils/string` — never inline slice + ellipsis
- `backoffWithJitter(attempt, retryAfterMs, options?)` / `parseRetryAfter(header)` from `@sim/utils/retry` — shared retry pacing; never reimplement exponential backoff inline
- **Deployment flags in the browser**: client code inside a workspace reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never `isHosted`/`isBillingEnabled`/... from `env-flags`. The constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit; the reader is seeded from the server-resolved workspace host context instead. Server code keeps reading `env-flags`
- **Deployment flags in the browser**: client code inside a workspace, organization, or standalone settings surface reads `hosted`, `billingEnabled`, `chatEnabled`, and the enterprise feature set through `useDeploymentShape()` (components) or `getDeploymentShape()` (block conditions, stores, helpers) from `@/lib/core/config/deployment-shape`, never `isHosted`/`isBillingEnabled`/... from `env-flags`. The constants freeze at module init from the root layout's `NEXT_PUBLIC_*` transport, which Next's bare 404 shell and `global-error` never emit; the reader is seeded from the server-resolved workspace host context, organization layout, or standalone settings layout instead. Server code keeps reading `env-flags`
- **Package Manager**: Use `bun` and `bunx`, not `npm` and `npx`
- **Type-checking**: Run `bun run type-check` (per workspace) or `bunx turbo run type-check` (all of them). Do not remove the `@typescript/native` alias from the root `devDependencies` — nothing imports it, but it is what makes a bare `tsc` resolve to the native TypeScript 7 compiler instead of the ~10x slower JavaScript TypeScript 6 one that `@typescript/typescript6` pulls in transitively. `bun run check:native-typecheck` enforces this

Expand Down
2 changes: 0 additions & 2 deletions apps/docs/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,6 @@ div.flex.flex-row.items-start.bg-fd-secondary.border.rounded-lg.text-xs {
display: none !important;
}

/* Method+path bar — cleaner, lighter styling like Gumloop.
Override bg-fd-card CSS variable directly for reliability. */
#nd-page:has(.api-page-header) div.flex.flex-row.items-center.rounded-xl.border.not-prose {
--color-fd-card: var(--surface-3) !important;
background-color: var(--surface-3) !important;
Expand Down
82 changes: 26 additions & 56 deletions apps/docs/components/ui/action-media.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import { useRef, useState } from 'react'
import { Lightbox } from '@sim/emcn'
import { cn, getAssetUrl } from '@/lib/utils'
import { Lightbox } from './lightbox'

interface ActionImageProps {
src: string
Expand All @@ -17,10 +17,6 @@ interface ActionVideoProps {
}

export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProps) {
const [isLightboxOpen, setIsLightboxOpen] = useState(false)

const openLightbox = () => setIsLightboxOpen(true)

const image = (
<img
src={src}
Expand All @@ -32,42 +28,28 @@ export function ActionImage({ src, alt, enableLightbox = true }: ActionImageProp
/>
)

if (!enableLightbox) return image

return (
<>
{enableLightbox ? (
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${alt} in media viewer`}
className='group inline-block cursor-pointer rounded p-0 text-left'
>
{image}
</button>
) : (
image
)}
{enableLightbox && (
<Lightbox
isOpen={isLightboxOpen}
onClose={() => setIsLightboxOpen(false)}
src={src}
alt={alt}
type='image'
/>
)}
</>
<Lightbox src={src} alt={alt}>
<button
type='button'
aria-label={`Open ${alt} in media viewer`}
className='group inline-block cursor-pointer rounded p-0 text-left'
>
{image}
</button>
</Lightbox>
)
}

export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const startTimeRef = useRef(0)
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
const [startTime, setStartTime] = useState(0)
const resolvedSrc = getAssetUrl(src)

const openLightbox = () => {
startTimeRef.current = videoRef.current?.currentTime ?? 0
setIsLightboxOpen(true)
setStartTime(videoRef.current?.currentTime ?? 0)
}

const video = (
Expand All @@ -85,30 +67,18 @@ export function ActionVideo({ src, alt, enableLightbox = true }: ActionVideoProp
/>
)

if (!enableLightbox) return video

return (
<>
{enableLightbox ? (
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${alt} in media viewer`}
className='group inline-block cursor-pointer rounded p-0 text-left'
>
{video}
</button>
) : (
video
)}
{enableLightbox && (
<Lightbox
isOpen={isLightboxOpen}
onClose={() => setIsLightboxOpen(false)}
src={src}
alt={alt}
type='video'
startTime={startTimeRef.current}
/>
)}
</>
<Lightbox src={resolvedSrc} alt={alt} type='video' startTime={startTime}>
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${alt} in media viewer`}
className='group inline-block cursor-pointer rounded p-0 text-left'
>
{video}
</button>
</Lightbox>
)
}
42 changes: 13 additions & 29 deletions apps/docs/components/ui/image.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
'use client'

import { useState } from 'react'
import { Lightbox } from '@sim/emcn'
import NextImage, { type ImageProps as NextImageProps } from 'next/image'
import { Lightbox } from '@/components/ui/lightbox'
import { cn } from '@/lib/utils'

interface ImageProps extends Omit<NextImageProps, 'className'> {
Expand All @@ -17,9 +16,7 @@ export function Image({
src,
...props
}: ImageProps) {
const [isLightboxOpen, setIsLightboxOpen] = useState(false)

const openLightbox = () => setIsLightboxOpen(true)
const lightboxSrc = typeof src === 'string' ? src : 'default' in src ? src.default.src : src.src

const image = (
<NextImage
Expand All @@ -34,30 +31,17 @@ export function Image({
/>
)

return (
<>
{enableLightbox ? (
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${alt} in media viewer`}
className='group contents'
>
{image}
</button>
) : (
image
)}
if (!enableLightbox) return image

{enableLightbox && (
<Lightbox
isOpen={isLightboxOpen}
onClose={() => setIsLightboxOpen(false)}
src={typeof src === 'string' ? src : String(src)}
alt={alt}
type='image'
/>
)}
</>
return (
<Lightbox src={lightboxSrc} alt={alt}>
<button
type='button'
aria-label={`Open ${alt || 'image'} in media viewer`}
className='group contents'
>
{image}
</button>
</Lightbox>
)
}
104 changes: 0 additions & 104 deletions apps/docs/components/ui/lightbox.tsx

This file was deleted.

45 changes: 15 additions & 30 deletions apps/docs/components/ui/video.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import { useEffect, useRef, useState } from 'react'
import { Lightbox } from '@sim/emcn'
import { cn, getAssetUrl } from '@/lib/utils'
import { Lightbox } from './lightbox'

interface VideoProps {
src: string
Expand All @@ -28,8 +28,7 @@ export function Video({
height,
}: VideoProps) {
const videoRef = useRef<HTMLVideoElement>(null)
const startTimeRef = useRef(0)
const [isLightboxOpen, setIsLightboxOpen] = useState(false)
const [startTime, setStartTime] = useState(0)
const [isInView, setIsInView] = useState(false)

useEffect(() => {
Expand All @@ -55,8 +54,7 @@ export function Video({
}, [])

const openLightbox = () => {
startTimeRef.current = videoRef.current?.currentTime ?? 0
setIsLightboxOpen(true)
setStartTime(videoRef.current?.currentTime ?? 0)
}

const video = (
Expand All @@ -78,31 +76,18 @@ export function Video({
/>
)

return (
<>
{enableLightbox ? (
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${src} in media viewer`}
className='group contents'
>
{video}
</button>
) : (
video
)}
if (!enableLightbox) return video

{enableLightbox && (
<Lightbox
isOpen={isLightboxOpen}
onClose={() => setIsLightboxOpen(false)}
src={src}
alt={`Video: ${src}`}
type='video'
startTime={startTimeRef.current}
/>
)}
</>
return (
<Lightbox src={getAssetUrl(src)} alt={`Video: ${src}`} type='video' startTime={startTime}>
<button
type='button'
onClick={openLightbox}
aria-label={`Open ${src} in media viewer`}
className='group contents'
>
{video}
</button>
</Lightbox>
)
}
Loading
Loading