From 169de8b634bee6ce3765d19c96593054d69bf657 Mon Sep 17 00:00:00 2001 From: reoring Date: Mon, 9 Nov 2026 10:48:08 +0900 Subject: [PATCH] fix: adopt project IAM access from appserver-go v0.3.0 --- README.md | 5 ++++- cmd/server/internal/api/api.go | 8 ++----- cmd/server/internal/api/api_test.go | 22 +++++++------------ .../internal/api/directory_cutover_test.go | 1 - cmd/server/internal/api/groups.go | 5 ++--- cmd/server/server_test.go | 6 ++--- go.mod | 2 +- go.sum | 4 ++-- .../stories/project-non-member.mock.spec.ts | 17 +++++--------- web/src/api/console.ts | 3 --- web/src/copy/shell.en.ts | 6 +---- web/src/copy/shell.ts | 6 +---- web/src/dev/mock-console.test.ts | 4 ++-- web/src/dev/mock-console.ts | 14 ++++-------- web/src/dev/mock-platform.ts | 10 ++------- web/src/features/admin/platform-page.tsx | 3 +-- .../features/build/use-build-session.test.tsx | 2 +- web/src/shell/shell.tsx | 2 +- 18 files changed, 39 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index 6def2d1..6d5ebfd 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Builder Console keeps app metadata, access requests, comments, and sharing polic ## Repository -- `cmd/server/`: Go server and tests, using `github.com/appthrust/appserver-go v0.2.0`. +- `cmd/server/`: Go server and tests, using `github.com/appthrust/appserver-go v0.3.0`. - `web/`: Vite/React SPA, unit tests, in-memory development APIs, and Playwright stories. - `catalog/entry.yaml`: ApplicationStackDefinition, including the Directory dependency, PostgreSQL connection, identity access settings, probes, and existing version floors. @@ -104,6 +104,9 @@ and the `/api/*` Platform proxy retain their existing contracts. ## Publication +Next release: **v0.4.0**. Membership and app-creation permissions come from +Project IAM access; non-member notices and settings no longer name an owning Team. + Only a `v` tag starts publication. The initial standalone release is **v0.3.0**, stream **0.3**, at `ghcr.io/appthrust/builder-console`. Do not reuse `0.1.0` or `0.2.0`: both served Platform-image floors remain verbatim in the committed diff --git a/cmd/server/internal/api/api.go b/cmd/server/internal/api/api.go index 48851f1..1e8ef10 100644 --- a/cmd/server/internal/api/api.go +++ b/cmd/server/internal/api/api.go @@ -45,7 +45,7 @@ type request struct { groups []domain.Group effectiveGroupIDs []string directory *directoryview.Session - access platform.TeamAccess + access platform.ProjectAccess bearer string platformUnavailable bool } @@ -200,11 +200,7 @@ func (a *API) withRequest(ctx context.Context, claims identity.Identity, bearer } } if state.access.NotMember && !me { - message := "This Project belongs to a Team; ask its administrator to add you" - if state.access.TeamSlug != "" { - message = "This Project belongs to Team " + state.access.TeamSlug - } - return httpx.Fail(403, "not_project_member", message) + return httpx.Fail(403, "not_project_member", "You are not a member of this Project; ask its administrator to add you") } } state.groups, err = a.directory.Groups(ctx, bearer) diff --git a/cmd/server/internal/api/api_test.go b/cmd/server/internal/api/api_test.go index fe818b9..50fc568 100644 --- a/cmd/server/internal/api/api_test.go +++ b/cmd/server/internal/api/api_test.go @@ -59,18 +59,12 @@ func newHarness(t *testing.T) *harness { role = "Editor" } switch r.URL.Path { - case "/api/v1/projects/pj-test": + case "/api/v1/projects/pj-test/access": if status := int(h.projectStatus.Load()); status != 0 { w.WriteHeader(status) return } - httpx.WriteJSON(w, 200, map[string]string{"teamSlug": "test-team"}) - case "/api/v1/teams": - httpx.WriteJSON(w, 200, map[string]any{"items": []any{map[string]string{"id": "team-id", "slug": "test-team", "role": role}}}) - case "/api/v1/teams/team-id/members": - httpx.WriteJSON(w, 200, map[string]any{"items": []any{map[string]string{"id": "one", "email": "admin@example.test", "name": "Admin"}}}) - case "/api/v1/teams/team-id/directory-users": - httpx.WriteJSON(w, 200, map[string]any{"items": []any{map[string]string{"id": "duplicate", "email": "admin@example.test", "name": "Admin"}, map[string]string{"id": "two", "email": "person@example.test", "name": "Person"}}}) + httpx.WriteJSON(w, 200, map[string]any{"projectId": "pj-test", "member": true, "role": role, "platformRoles": []string{}}) case "/api/v1/projects/pj-test/source-connections": httpx.WriteJSON(w, 200, map[string]any{"data": []any{map[string]string{"id": "pending", "status": "incomplete"}, map[string]string{"id": "github", "status": "ready"}}}) case "/api/v1/registry-connections": @@ -198,7 +192,7 @@ func TestProjectNonMemberRoutesIntegration(t *testing.T) { call[meResponse](h, "admin", "GET", "/me", nil, 200) h.projectStatus.Store(int32(status)) me := call[meResponse](h, "outsider", "GET", "/me", nil, 200) - if !me.NotMember || me.TeamSlug != "test-team" || me.PlatformUnavailable || + if !me.NotMember || me.PlatformUnavailable || me.CanCreateApps || me.IsConsoleAdmin || me.Person.ID != person.ID || len(me.EffectiveGroupIDs) != 0 { t.Fatalf("non-member me leaked roles or memberships: %+v", me) } @@ -220,7 +214,7 @@ func TestProjectNonMemberRoutesIntegration(t *testing.T) { {"GET", "/admin/platform"}, } { failure := call[httpx.Error](h, "outsider", route.method, route.path, nil, 403) - if failure.Code != "not_project_member" || failure.Message != "This Project belongs to Team test-team" { + if failure.Code != "not_project_member" || failure.Message != "You are not a member of this Project; ask its administrator to add you" { t.Fatalf("%s %s: %+v", route.method, route.path, failure) } } @@ -228,16 +222,16 @@ func TestProjectNonMemberRoutesIntegration(t *testing.T) { } } -func TestProjectNonMemberWithoutKnownTeamIntegration(t *testing.T) { +func TestProjectNonMemberBeforeFirstMemberLookupIntegration(t *testing.T) { h := newHarness(t) h.projectStatus.Store(http.StatusForbidden) me := call[meResponse](h, "outsider", "GET", "/me", nil, 200) - if !me.NotMember || me.TeamSlug != "" || me.PlatformUnavailable || me.CanCreateApps || me.IsConsoleAdmin { + if !me.NotMember || me.PlatformUnavailable || me.CanCreateApps || me.IsConsoleAdmin { t.Fatalf("non-member before successful Project lookup: %+v", me) } failure := call[httpx.Error](h, "outsider", "GET", "/apps", nil, 403) - if failure.Code != "not_project_member" || failure.Message != "This Project belongs to a Team; ask its administrator to add you" { - t.Fatalf("unknown owning Team: %+v", failure) + if failure.Code != "not_project_member" || failure.Message != "You are not a member of this Project; ask its administrator to add you" { + t.Fatalf("non-member notice: %+v", failure) } } diff --git a/cmd/server/internal/api/directory_cutover_test.go b/cmd/server/internal/api/directory_cutover_test.go index 02db6d7..0200808 100644 --- a/cmd/server/internal/api/directory_cutover_test.go +++ b/cmd/server/internal/api/directory_cutover_test.go @@ -28,7 +28,6 @@ func TestMeCompositionAndUnknownHydration(t *testing.T) { require.True(t, me.CanCreateApps) require.Equal(t, []string{group.ID}, me.EffectiveGroupIDs) require.Equal(t, []string{group.ID}, me.ManagedGroupIDs) - require.Equal(t, "test-team", me.TeamSlug) require.Equal(t, "pj-test", me.ProjectID) require.Equal(t, "https://directory.example.test", me.DirectoryPublicURL) require.False(t, me.Migration.Pending) diff --git a/cmd/server/internal/api/groups.go b/cmd/server/internal/api/groups.go index 0269056..7d25095 100644 --- a/cmd/server/internal/api/groups.go +++ b/cmd/server/internal/api/groups.go @@ -16,7 +16,6 @@ type meResponse struct { ProjectID string `json:"projectId"` PlatformUnavailable bool `json:"platformUnavailable,omitempty"` NotMember bool `json:"notMember,omitempty"` - TeamSlug string `json:"teamSlug"` DirectoryPublicURL string `json:"directoryPublicURL"` Migration store.MigrationStatus `json:"migration"` } @@ -38,7 +37,7 @@ func (a *API) me(s *request) (any, int, error) { if resolveErr != nil { return nil, 0, resolveErr } - return meResponse{Person: s.person, Roles: domain.Roles(s.access.Roles), EffectiveGroupIDs: effective, ManagedGroupIDs: managed, ProjectID: a.platform.ProjectID, PlatformUnavailable: s.platformUnavailable, NotMember: s.access.NotMember, TeamSlug: s.access.TeamSlug, DirectoryPublicURL: binding.PublicURL, Migration: migration}, 200, err + return meResponse{Person: s.person, Roles: domain.Roles(s.access.Roles), EffectiveGroupIDs: effective, ManagedGroupIDs: managed, ProjectID: a.platform.ProjectID, PlatformUnavailable: s.platformUnavailable, NotMember: s.access.NotMember, DirectoryPublicURL: binding.PublicURL, Migration: migration}, 200, err } func (a *API) migrateDirectory(s *request) (any, int, error) { if !s.actor.IsConsoleAdmin { @@ -61,7 +60,7 @@ func (a *API) migrateDirectory(s *request) (any, int, error) { return result, 200, s.tx.SaveDirectoryMigration(result) } func (a *API) settings(s *request) (any, int, error) { - settings, err := a.platform.Settings(s.r.Context(), s.bearer, s.access) + settings, err := a.platform.Settings(s.r.Context(), s.bearer) if err != nil { return nil, 0, upstreamError() } diff --git a/cmd/server/server_test.go b/cmd/server/server_test.go index febc7b8..f9f4d81 100644 --- a/cmd/server/server_test.go +++ b/cmd/server/server_test.go @@ -520,14 +520,12 @@ func TestServedOpenAPIEqualsCommittedContract(t *testing.T) { t.Run(tc.name, func(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { - case "/api/v1/projects/pj-test": + case "/api/v1/projects/pj-test/access": if !tc.member { w.WriteHeader(http.StatusForbidden) return } - writeJSON(w, 200, map[string]string{"teamSlug": "test-team"}) - case "/api/v1/teams": - writeJSON(w, 200, map[string]any{"items": []any{map[string]string{"id": "team-id", "slug": "test-team", "role": "Viewer"}}}) + writeJSON(w, 200, map[string]any{"projectId": "pj-test", "member": true, "role": "Viewer", "platformRoles": []string{}}) default: t.Errorf("unexpected platform route %s", r.URL.Path) w.WriteHeader(http.StatusNotFound) diff --git a/go.mod b/go.mod index dac5625..50cde9e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/appthrust/builder-console go 1.26.0 require ( - github.com/appthrust/appserver-go v0.2.0 + github.com/appthrust/appserver-go v0.3.0 github.com/google/uuid v1.6.0 github.com/lib/pq v1.12.3 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 0b6a71b..602d705 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/appthrust/appserver-go v0.2.0 h1:TLb4pzbTXy3uMER5vQAXdzeeOTLxuNYEUXXvTAVzWm8= -github.com/appthrust/appserver-go v0.2.0/go.mod h1:0KSf9RQZ6OxBcE0lxYzzjKF37nfwbKps3wXUGlE0oFo= +github.com/appthrust/appserver-go v0.3.0 h1:zxOz0hkspZSXjLq3KWGDLkZIz42Jag6wqRujZ8lTru0= +github.com/appthrust/appserver-go v0.3.0/go.mod h1:0KSf9RQZ6OxBcE0lxYzzjKF37nfwbKps3wXUGlE0oFo= github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE= github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= diff --git a/web/e2e/stories/project-non-member.mock.spec.ts b/web/e2e/stories/project-non-member.mock.spec.ts index be64d68..39d6117 100644 --- a/web/e2e/stories/project-non-member.mock.spec.ts +++ b/web/e2e/stories/project-non-member.mock.spec.ts @@ -1,19 +1,18 @@ import { test, expect, seed } from '../support/fixtures' -test('explains Project non-membership with its Team, hides app actions, and lets the user log out @mock @story', async ({ page, request, mock }) => { - const teamSlug = 'owning-project-team' +test('explains Project non-membership, hides app actions, and lets the user log out @mock @story', async ({ page, request, mock }) => { const title = 'この基盤(Project)のメンバーではありません' - const body = `この基盤は Team「${teamSlug}」が管理しています。管理者に追加を依頼してください。` - await mock.console('not-member', { teamSlug }) + const body = 'この基盤の管理者に追加を依頼してください。' + await mock.console('not-member', {}) const me = await request.get('/api/console/me') expect(me.status()).toBe(200) expect(await me.json()).toMatchObject({ - notMember: true, teamSlug, isConsoleAdmin: false, canCreateApps: false, + notMember: true, isConsoleAdmin: false, canCreateApps: false, }) const apps = await request.get('/api/console/apps') expect(apps.status()).toBe(403) - expect(await apps.json()).toEqual({ error: 'not_project_member', message: `This Project belongs to Team ${teamSlug}` }) + expect(await apps.json()).toEqual({ error: 'not_project_member', message: 'You are not a member of this Project; ask its administrator to add you' }) await page.goto('/') const main = page.getByRole('main') @@ -36,12 +35,6 @@ test('explains Project non-membership with its Team, hides app actions, and lets await expect(main.getByText(body, { exact: true })).toBeVisible() await expect(page.getByRole('button', { name: 'メニューを開く', exact: true })).toHaveCount(0) - // Before the server has read the Project successfully, it must not invent a Team name. - await mock.console('not-member', { teamSlug: '' }) - await page.reload() - await expect(main.getByText('管理者に追加を依頼してください。', { exact: true })).toBeVisible() - await expect(main.getByText(body, { exact: true })).toHaveCount(0) - // The edge owns logout; prove this stays a full document navigation, not an API call. await page.route('**/logout', (route) => route.fulfill({ contentType: 'text/html', body: '

Signed out

' })) await page.getByRole('button', { name: 'ユーザーメニュー', exact: true }).click() diff --git a/web/src/api/console.ts b/web/src/api/console.ts index 8c00d5d..07e3a1e 100644 --- a/web/src/api/console.ts +++ b/web/src/api/console.ts @@ -119,8 +119,6 @@ export type Me = { platformUnavailable?: boolean /** The Project rejected this account; this is not a Platform outage. */ notMember?: boolean - /** Owning Team from the latest successful Project read; empty until one succeeds. */ - teamSlug: string /** Public origin of the Directory built-in app (profiles, group management, avatars); empty when not linked. */ directoryPublicURL: string migration: DirectoryMigration @@ -132,7 +130,6 @@ export type Resolved = { kind: 'group' | 'app' | 'entry'; id: string; currentPat export type DirectoryUser = { id: string; email: string; displayName: string } export type PlatformSettings = { projectId: string - teamSlug: string | null github: { connected: boolean; connectionId: string | null } registry: { connected: boolean; connectionId: string | null } defaultTemplateId: string diff --git a/web/src/copy/shell.en.ts b/web/src/copy/shell.en.ts index 1c8d8a2..955682e 100644 --- a/web/src/copy/shell.en.ts +++ b/web/src/copy/shell.en.ts @@ -55,9 +55,7 @@ export const shellEn: typeof shell = { platformUnavailable: 'App creation is unavailable', platformUnavailableBody: 'Wait a moment, then reload the page.', notMemberTitle: 'You are not a project member', - notMemberBody: (teamSlug?: string) => teamSlug - ? `This project is managed by the team “${teamSlug}”. Ask an admin to add you.` - : 'Ask an admin to add you.', + notMemberBody: 'Ask a project admin to add you.', }, home: { title: 'Home', @@ -142,8 +140,6 @@ export const shellEn: typeof shell = { registryOff: 'Not connected yet. Ask your workspace admin to set this up.', team: 'App creators', teamBody: 'People with app creation access can create apps here. Ask your workspace admin to add someone.', - teamName: 'Team', - teamUnknown: 'Unavailable', project: 'Project', advanced: 'Advanced', connected: 'Connected', diff --git a/web/src/copy/shell.ts b/web/src/copy/shell.ts index 584211b..68fbcf4 100644 --- a/web/src/copy/shell.ts +++ b/web/src/copy/shell.ts @@ -56,9 +56,7 @@ export const shell = { platformUnavailable: 'いまアプリの作成状態を確認できません', platformUnavailableBody: 'しばらくしてからページを再読み込みしてください。', notMemberTitle: 'この基盤(Project)のメンバーではありません', - notMemberBody: (teamSlug?: string) => teamSlug - ? `この基盤は Team「${teamSlug}」が管理しています。管理者に追加を依頼してください。` - : '管理者に追加を依頼してください。', + notMemberBody: 'この基盤の管理者に追加を依頼してください。', }, home: { title: 'ホーム', @@ -143,8 +141,6 @@ export const shell = { registryOff: 'まだ連携していません。基盤の担当者に設定を依頼してください。', team: '作れる人', teamBody: 'アプリを作れるのは、基盤で「作れる人」に登録された人です。追加は基盤の担当者が行います。', - teamName: 'チーム', - teamUnknown: '確認できません', project: 'プロジェクト', advanced: '詳しい人向け', connected: '連携済み', diff --git a/web/src/dev/mock-console.test.ts b/web/src/dev/mock-console.test.ts index ec5b085..ded9923 100644 --- a/web/src/dev/mock-console.test.ts +++ b/web/src/dev/mock-console.test.ts @@ -68,7 +68,7 @@ test('platform reset restores seeded stacks, lifecycle state, toggles and the ID `${project}/applications/nippo/branches/production/components/nippo-web/env-vars`, `${project}/application-templates`, `${project}/source-connections/github-source-207cb7ce/repositories`, - '/api/v1/teams', + `${project}/access`, '/api/v1/registry-connections', `${project}/application-drafts/creation-tools`, ] @@ -125,7 +125,7 @@ test('platform reset restores seeded stacks, lifecycle state, toggles and the ID process.env.MOCK_REGISTRY = 'off' process.env.MOCK_CODEX = 'off' expect((await call('POST', `${mock}/reset`)).status).toBe(200) - expect((await call('GET', '/api/v1/teams')).body.items[0].role).toBe('Viewer') + expect((await call('GET', `${project}/access`)).body).toEqual({ projectId: 'commerce', member: true, role: 'Viewer', platformRoles: [] }) expect((await call('GET', '/api/v1/registry-connections')).body.data).toEqual([]) expect((await call('GET', `${project}/application-drafts/creation-tools`)).body.codexAvailable).toBe(false) } finally { diff --git a/web/src/dev/mock-console.ts b/web/src/dev/mock-console.ts index f425b8b..311a042 100644 --- a/web/src/dev/mock-console.ts +++ b/web/src/dev/mock-console.ts @@ -8,7 +8,7 @@ // enough to exercise the UI; the real rules live in cmd/server. // `POST /api/console/__mock/bare-500 {times?}` makes the next writes answer a // bare gateway 500 (no JSON envelope), as during a console rollout. -// `POST /api/console/__mock/not-member {notMember?, teamSlug?}` denies Project +// `POST /api/console/__mock/not-member {notMember?}` denies Project // access (default true); /me still answers with the non-member notice metadata. // `POST /api/console/__mock/caller {personId, role?}` switches signed-in people // (default role Viewer) for requester/approver stories; reset restores Yamada. @@ -208,7 +208,6 @@ function seedConsole() { role: (process.env.MOCK_ROLE as Role | undefined) ?? 'Admin', platformUnavailable: process.env.MOCK_PLATFORM_UNAVAILABLE === '1', notMember: false, - teamSlug: 'commerce-team', projectId: process.env.MOCK_PROJECT_ID ?? 'commerce', people, directoryOnly, @@ -368,7 +367,6 @@ function me(): Me { isConsoleAdmin: isAdmin(), canCreateApps: !state.notMember && !state.platformUnavailable && (state.role === 'Admin' || state.role === 'Editor'), projectId: state.projectId, - teamSlug: state.teamSlug, directoryPublicURL: requestOrigin, migration: state.migration, ...(state.notMember ? { notMember: true } : {}), @@ -560,10 +558,9 @@ export async function consoleRoutes(request: Request, url: URL): Promise(request) + const input = await body<{ notMember?: boolean }>(request) state.notMember = input.notMember ?? true - state.teamSlug = input.teamSlug ?? state.teamSlug - return json({ notMember: state.notMember, teamSlug: state.teamSlug }) + return json({ notMember: state.notMember }) } // Old console drafts (stackId null): the abandoned 作りかけ row. if (head === '__mock' && id === 'app-draft' && method === 'POST') { @@ -606,9 +603,7 @@ export async function consoleRoutes(request: Request, url: URL): Promise.appthrust.test`). */ const BASE_DOMAIN = `${MOCK_PROJECT_ID}.appthrust.test` const AI_QUOTA_ERROR = 'Codex task failed: Codex error: stream disconnected before completion: You have no credits remaining. Add credits to continue using the API at ' @@ -1744,13 +1743,8 @@ const routes: Array<[method: string, pattern: RegExp, handler: Handler]> = [ if (typeof body.interruptSecondTask === 'boolean') state.implementationRun.interruptSecondTask = body.interruptSecondTask return json(state.implementationRun) }], - ['GET', new RegExp(`^/api/v1/projects/([^/]+)$`), (m) => json({ id: m[1], name: m[1], teamSlug: TEAM_SLUG })], - ['GET', /^\/api\/v1\/teams$/, () => json({ items: [{ id: 'team-commerce', slug: TEAM_SLUG, name: 'Commerce', plan: 'team', role: state.role }] })], - [ - 'GET', - /^\/api\/v1\/teams\/([^/]+)\/directory-users$/, - () => json({ items: [{ id: 'u-new-1', email: 'kobayashi@acme.example', name: '小林 真' }, { id: 'u-new-2', email: 'kato@acme.example', name: '加藤 葵' }] }), - ], + ['GET', new RegExp(`^/api/v1/projects/([^/]+)$`), (m) => json({ id: m[1], name: m[1] })], + ['GET', new RegExp(`^/api/v1/projects/([^/]+)/access$`), (m) => json({ projectId: m[1], member: true, role: state.role, platformRoles: [] })], ['GET', new RegExp(`^${P}/source-connections$`), () => json(list(sourceConnections))], [ 'GET', diff --git a/web/src/features/admin/platform-page.tsx b/web/src/features/admin/platform-page.tsx index db3c564..a9972bf 100644 --- a/web/src/features/admin/platform-page.tsx +++ b/web/src/features/admin/platform-page.tsx @@ -92,7 +92,7 @@ export function AdminPlatformPage() { } title={copy.admin.team} - state={settings.data.teamSlug ? 'ready' : 'unknown'} + state={me.canCreateApps ? 'ready' : 'missing'} > {copy.admin.teamBody} {me.canCreateApps ? text.platform.teamYou : text.platform.teamNotYou} @@ -103,7 +103,6 @@ export function AdminPlatformPage() {

{text.platform.advancedLead}

- {settings.data.github.connectionId ? : null} {settings.data.registry.connectionId ? : null} {domain ? : null} diff --git a/web/src/features/build/use-build-session.test.tsx b/web/src/features/build/use-build-session.test.tsx index 057aaf3..4d9a94b 100644 --- a/web/src/features/build/use-build-session.test.tsx +++ b/web/src/features/build/use-build-session.test.tsx @@ -23,7 +23,7 @@ import { useBuildSession } from './use-build-session' const me: Me = { projectId: 'project-session-test', person: { id: 'person-1', subject: 'owner-1', pending: false, email: 'owner@example.test', displayName: 'Owner', avatarVersion: null }, effectiveGroupIds: ['sales'], managedGroupIds: [], isConsoleAdmin: false, canCreateApps: true, - teamSlug: 'team', directoryPublicURL: '', migration: { pending: false, people: 0, groups: 0, memberships: 0 }, + directoryPublicURL: '', migration: { pending: false, people: 0, groups: 0, memberships: 0 }, } const at = '2026-10-26T00:00:00Z' const turn = (id: string, userMessage: string, minute: number): ApplicationDraftConversationTurn => ({ diff --git a/web/src/shell/shell.tsx b/web/src/shell/shell.tsx index 6145c70..3ce9583 100644 --- a/web/src/shell/shell.tsx +++ b/web/src/shell/shell.tsx @@ -58,7 +58,7 @@ export function Shell({ children }: { children: ReactNode }) { {headerOnly || account ? children : (

{copy.topbar.notMemberTitle}

-

{copy.topbar.notMemberBody(me.teamSlug)}

+

{copy.topbar.notMemberBody}

)}