Skip to content

Commit 60c9ea3

Browse files
committed
Mount the Platform zone at /org so the marketing page keeps its route and indexing
A page at /platform wins over the afterFiles rewrite, so the zone was unreachable, and listing it as noindex de-indexed the public page. Also fails the handoff closed on an image built without the rewrite, repairs Home's Search redirect after the chat merge, and clears the audit findings.
1 parent 88f58ec commit 60c9ea3

23 files changed

Lines changed: 76 additions & 50 deletions

‎apps/sim/app/workspace/[workspaceId]/logs/search-params.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import {
2020
* the raw input value (consumers trim on read).
2121
*/
2222

23-
export { parseAsDateString }
24-
2523
const DEFAULT_TIME_RANGE: TimeRange = 'All time'
2624

2725
const VALID_LEVELS = ['error', 'info', 'running', 'pending'] as const

‎apps/sim/lib/navigation/paths.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ export function isAppSurfacePath(pathname: string): boolean {
8787
}
8888

8989
/**
90-
* Non-app routes that must never appear in search results: the Platform zone,
90+
* Non-app routes that must never appear in search results: the Platform zone at `/org`,
9191
* deployed chats, paused-run resume links, invitations, unsubscribe links, shared
9292
* files, the legacy `/w` redirects, the design playground, and account, self-host,
9393
* and upgrade utility pages.
9494
*/
9595
const NOINDEX_PATH_ROOTS = [
96-
'/platform',
96+
'/org',
9797
'/sim',
9898
'/chat',
9999
'/extension',

‎apps/sim/lib/platform/rollout.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ const logger = createLogger('PlatformRollout')
77

88
/**
99
* Whether this organization's legacy routes hand off to the Platform. The flag is never read on a
10-
* deployment with no Platform zone. A failed read keeps the organization on the legacy surface.
10+
* deployment with no Platform zone, including an image built without the rewrite, whose handoff
11+
* would land on a 404. A failed read keeps the organization on the legacy surface.
1112
*/
1213
export async function isPlatformOrgUiEnabled(organizationId: string): Promise<boolean> {
13-
if (!env.SIM_PLATFORM_ORIGIN) return false
14+
if (!env.SIM_PLATFORM_ORIGIN || process.env.SIM_PLATFORM_ZONE_REWRITE !== '1') return false
1415
try {
1516
return await isFeatureEnabled('platform-org-ui', { orgId: organizationId })
1617
} catch (error) {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { existsSync, readdirSync } from 'node:fs'
2+
import path from 'node:path'
3+
import { describe, expect, it } from 'vitest'
4+
import { isNoindexPath } from '@/lib/navigation/paths'
5+
import { mapLegacyOrganizationUrl } from '@/lib/platform/routes'
6+
7+
const APP_ROOT = path.resolve(__dirname, '../../app')
8+
9+
describe('Platform zone mount', () => {
10+
it('leaves the marketing /platform page indexable', () => {
11+
expect(isNoindexPath('/platform')).toBe(false)
12+
expect(isNoindexPath('/org/home')).toBe(true)
13+
})
14+
15+
it('owns a path no main-app page serves, since a static page beats an afterFiles rewrite', () => {
16+
const groups = readdirSync(APP_ROOT).filter((entry) => entry.startsWith('('))
17+
for (const root of ['.', ...groups]) {
18+
expect(existsSync(path.join(APP_ROOT, root, 'org'))).toBe(false)
19+
}
20+
})
21+
22+
it('hands legacy organization routes to the mount', () => {
23+
expect(mapLegacyOrganizationUrl('/o/org-1/home', 'org-1')).toBe('/org/home')
24+
})
25+
})

‎apps/sim/lib/platform/routes.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
* server components, and client code all read it.
44
*/
55

6-
/** Where the main app's `/platform` rewrite in `next.config.ts` mounts the zone. */
7-
const PLATFORM_BASE_PATH = '/platform'
6+
/** Where the main app's `/org` rewrite in `next.config.ts` mounts the zone. */
7+
const PLATFORM_BASE_PATH = '/org'
88

99
/**
1010
* Carries the requested `/o/...` URL from the proxy to the organization layout, which Next gives no

‎apps/sim/next.config.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const MARKDOWN_REQUEST = {
3232
const PLATFORM_ORIGIN = env.SIM_PLATFORM_ORIGIN ? new URL(env.SIM_PLATFORM_ORIGIN).origin : null
3333

3434
const nextConfig: NextConfig = {
35+
/** Lets the runtime handoff check that this build actually has the Platform rewrite. */
36+
env: { SIM_PLATFORM_ZONE_REWRITE: PLATFORM_ORIGIN ? '1' : '' },
3537
devIndicators: false,
3638
poweredByHeader: false,
3739
// Safe here since this repo's source is already fully public on GitHub -
@@ -728,8 +730,8 @@ const nextConfig: NextConfig = {
728730
...(PLATFORM_ORIGIN
729731
? [
730732
{
731-
source: '/platform/:path*',
732-
destination: `${PLATFORM_ORIGIN}/platform/:path*`,
733+
source: '/org/:path*',
734+
destination: `${PLATFORM_ORIGIN}/org/:path*`,
733735
},
734736
]
735737
: []),

‎apps/sim/platform/README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@ The same app serves three modes, resolved on the server:
2525
| `live` | `SIM_PLATFORM_API_ORIGIN`, `NEXT_PUBLIC_APP_URL` | The viewer's real organization, over the main app's `/api` |
2626
| `none` | Neither | Empty routes |
2727

28-
Deployed, the Platform is a zone at `/platform` on the main app's origin, so it shares the session cookie, `/api` and security headers:
28+
Deployed, the Platform is a zone at `/org` on the main app's origin, so it shares the session cookie, `/api` and security headers:
2929

3030
| Variable | Set on | Purpose |
3131
| --- | --- | --- |
32-
| `SIM_PLATFORM_BASE_PATH=/platform` | Platform, at build | Prefixes routes and assets |
32+
| `SIM_PLATFORM_BASE_PATH=/org` | Platform, at build | Prefixes routes and assets |
3333
| `SIM_PLATFORM_API_ORIGIN` | Platform, at runtime | Where server reads go, with the viewer's cookies |
3434
| `NEXT_PUBLIC_APP_URL` | Platform, at runtime | The main app's public origin, for login and legacy links |
35-
| `SIM_PLATFORM_ORIGIN` | Main app, at build and runtime | The `/platform` rewrite is baked at build; the organization redirect reads it at runtime |
35+
| `SIM_PLATFORM_ORIGIN` | Main app, at build and runtime | The `/org` rewrite is baked at build; the organization redirect reads it at runtime |
3636
| `platform-org-ui` flag | AppConfig, or `PLATFORM_ORG_UI` | Which organizations the legacy layout redirects here |
3737

3838
With none of these set, nothing changes for either app. The legacy layout redirects only flagged members, keeps its query string, and leaves unmapped routes on the legacy surface. To run live locally without the zone, start the main app, sign in at its origin, then start this app with `SIM_PLATFORM_API_ORIGIN=http://localhost:3000` and `NEXT_PUBLIC_APP_URL=http://localhost:3000`; with no base path it proxies `/api` itself.

‎apps/sim/platform/app/(shell)/_components/chat/cards/assistant-review.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import { useState } from 'react'
4+
import { getErrorMessage } from '@sim/utils/errors'
45
import styles from '@/platform/app/(shell)/_components/chat/cards/cards.module.css'
56
import type {
67
AssistantReceipt,
@@ -146,7 +147,7 @@ export function AssistantReview({ turnId }: { turnId: string }) {
146147
Object.fromEntries(Object.entries(current).filter(([id]) => !ids.includes(id)))
147148
)
148149
} catch (error) {
149-
setFailure(error instanceof Error ? error.message : 'Could not save this review')
150+
setFailure(getErrorMessage(error, 'Could not save this review'))
150151
}
151152
}
152153
if (status === 'error')

‎apps/sim/platform/checks/live/chat.spec.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function requireEnv(name: string): string {
77
}
88

99
async function signIn(page: Page) {
10-
await page.goto('/platform/home')
10+
await page.goto('/org/home')
1111
await page.locator('#email').fill(requireEnv('PLATFORM_LIVE_EMAIL'))
1212
await page.locator('#password').fill(requireEnv('PLATFORM_LIVE_PASSWORD'))
1313
await page.getByRole('button', { name: 'Sign in', exact: true }).click()
@@ -37,21 +37,21 @@ test('A chat outside the viewer’s access is not found rather than an empty cha
3737
page,
3838
}) => {
3939
await signIn(page)
40-
const response = await page.goto('/platform/chat/00000000-0000-4000-8000-000000000000')
40+
const response = await page.goto('/org/chat/00000000-0000-4000-8000-000000000000')
4141
expect(response?.status()).toBe(404)
4242
})
4343

4444
test('A resource in the URL survives a reload of the chat route', async ({ page }) => {
4545
await signIn(page)
46-
await page.goto('/platform/chat?resource=stale-id')
46+
await page.goto('/org/chat?resource=stale-id')
4747
await expect(page.getByRole('textbox')).toBeVisible()
4848
await expect(page).toHaveURL(/resource=stale-id/)
4949
})
5050

5151
test('A running turn streams, stops, and keeps what arrived', async ({ page }) => {
5252
test.skip(!orchestrator, 'Needs PLATFORM_LIVE_ORCHESTRATOR=1')
5353
await signIn(page)
54-
await page.goto('/platform/chat')
54+
await page.goto('/org/chat')
5555
await page.getByRole('textbox').fill('Count slowly from 1 to 50, one number per line.')
5656
await page.getByRole('button', { name: 'Send message' }).click()
5757
await expect(page).toHaveURL(/\/platform\/chat\/[^/?]+/, { timeout: 30_000 })
@@ -73,7 +73,7 @@ test('A message sent during a running turn waits in the queue and can be removed
7373
}) => {
7474
test.skip(!orchestrator, 'Needs PLATFORM_LIVE_ORCHESTRATOR=1')
7575
await signIn(page)
76-
await page.goto('/platform/chat')
76+
await page.goto('/org/chat')
7777
const field = page.getByRole('textbox')
7878
await field.fill('Count slowly from 1 to 50, one number per line.')
7979
await page.getByRole('button', { name: 'Send message' }).click()
@@ -92,7 +92,7 @@ test('A gated tool holds the turn until the prompt is answered', async ({ page }
9292
'Needs an orchestrator and a prompt that triggers a gated tool'
9393
)
9494
await signIn(page)
95-
await page.goto('/platform/chat')
95+
await page.goto('/org/chat')
9696
await page.getByRole('textbox').fill(requireEnv('PLATFORM_LIVE_APPROVAL_PROMPT'))
9797
await page.getByRole('button', { name: 'Send message' }).click()
9898
const allow = page.getByRole('button', { name: 'Allow', exact: true })

‎apps/sim/platform/checks/live/home.spec.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ function requireEnv(name: string): string {
77
}
88

99
async function signIn(page: Page) {
10-
await page.goto('/platform/home')
10+
await page.goto('/org/home')
1111
await page.locator('#email').fill(requireEnv('PLATFORM_LIVE_EMAIL'))
1212
await page.locator('#password').fill(requireEnv('PLATFORM_LIVE_PASSWORD'))
1313
await page.getByRole('button', { name: 'Sign in', exact: true }).click()
@@ -24,7 +24,7 @@ async function organizationId(page: Page): Promise<string> {
2424
test.describe('Home on real data', () => {
2525
test.beforeEach(async ({ page }) => {
2626
await signIn(page)
27-
await page.goto('/platform/home')
27+
await page.goto('/org/home')
2828
})
2929

3030
test('the Email and Calendar widgets each settle in a state the member can act on', async ({

0 commit comments

Comments
 (0)