@teamscala/service-boot 0.23.329 → 0.23.331
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +538 -522
- package/src/agreement-gate.d.ts +19 -0
- package/src/asset-cache-key.d.ts +11 -0
- package/src/asset-optimize.d.ts +17 -0
- package/src/asset-proxy.d.ts +11 -0
- package/src/asset-social-card.d.ts +17 -0
- package/src/attach-agreement-magic.d.ts +20 -0
- package/src/attach-portal-members.d.ts +23 -0
- package/src/attach-proposal-accept.d.ts +26 -0
- package/src/attach-proposal-magic.d.ts +18 -0
- package/src/attach-transfers-upload.d.ts +27 -0
- package/src/auth-gate.d.ts +28 -0
- package/src/auth-gate.ts +6 -68
- package/src/bind-page-handler.d.ts +18 -0
- package/src/build-client-assets/input-hashing.d.ts +25 -0
- package/src/build-client-assets/source-candidates.d.ts +27 -0
- package/src/build-client-assets.d.ts +50 -0
- package/src/build-client-assets.test.ts +1 -1
- package/src/build-client-assets.ts +1 -1
- package/src/compose-page-title.d.ts +41 -0
- package/src/content-gate/access.d.ts +15 -0
- package/src/content-gate/config-types.d.ts +99 -0
- package/src/content-gate/gate.d.ts +13 -0
- package/src/content-gate/parse-cookie.d.ts +9 -0
- package/src/content-gate/token.d.ts +48 -0
- package/src/content-page-floor.d.ts +39 -0
- package/src/discover-blocks.d.ts +28 -0
- package/src/discover-blocks.ts +1 -1
- package/src/expand-roster.d.ts +34 -0
- package/src/fn-routers/agreement-completion-email.d.ts +14 -0
- package/src/fn-routers/agreement-send-router.d.ts +82 -0
- package/src/fn-routers/ai-health-router.d.ts +35 -0
- package/src/fn-routers/auth-router.d.ts +47 -0
- package/src/fn-routers/cache-admin-router.d.ts +57 -0
- package/src/fn-routers/chat-session-router.d.ts +219 -0
- package/src/fn-routers/email-defaults.d.ts +1 -0
- package/src/fn-routers/email-link-ttl.d.ts +1 -0
- package/src/fn-routers/index.d.ts +18 -0
- package/src/fn-routers/index.ts +0 -2
- package/src/fn-routers/panel-data-router.d.ts +19 -0
- package/src/fn-routers/persist-chat-messages.d.ts +36 -0
- package/src/fn-routers/plans-router.d.ts +25 -0
- package/src/fn-routers/proposal-send-router.d.ts +58 -0
- package/src/fn-routers/public-agreement-router.d.ts +289 -0
- package/src/fn-routers/public-assessment-router.d.ts +68 -0
- package/src/fn-routers/public-gpt-session-router.d.ts +143 -0
- package/src/fn-routers/sign-in-flow-router.d.ts +107 -0
- package/src/fn-routers/task-sync-assign.d.ts +305 -0
- package/src/fn-routers/task-sync-assign.test.ts +1 -2
- package/src/fn-routers/task-sync-assign.ts +4 -4
- package/src/fn-routers/task-sync-bind.test.ts +18 -18
- package/src/fn-routers/task-sync-brief.d.ts +96 -0
- package/src/fn-routers/task-sync-brief.test.ts +17 -20
- package/src/fn-routers/task-sync-brief.ts +14 -15
- package/src/fn-routers/task-sync-close.d.ts +41 -0
- package/src/fn-routers/task-sync-dispatch.d.ts +61 -0
- package/src/fn-routers/task-sync-dispatch.test.ts +33 -51
- package/src/fn-routers/task-sync-dispatch.ts +124 -0
- package/src/fn-routers/task-sync-lane.d.ts +49 -0
- package/src/fn-routers/task-sync-lane.ts +125 -0
- package/src/fn-routers/task-sync-projection.d.ts +121 -0
- package/src/fn-routers/task-sync-projection.test.ts +0 -26
- package/src/fn-routers/task-sync-projection.ts +0 -51
- package/src/fn-routers/task-sync-rename.d.ts +91 -0
- package/src/fn-routers/task-sync-router.d.ts +444 -0
- package/src/fn-routers/task-sync-router.test.ts +2 -14
- package/src/fn-routers/task-sync-router.ts +1111 -0
- package/src/fn-routers/tasks-router.d.ts +25 -0
- package/src/fn-routers/user-preferences-router.d.ts +77 -0
- package/src/fn-routers/work-items-router.d.ts +102 -0
- package/src/gate-ttls.d.ts +6 -0
- package/src/gpt-gate.d.ts +25 -0
- package/src/handlers/bun-stable-watch.d.ts +1 -0
- package/src/handlers/pane-outbox-drain.d.ts +1 -0
- package/src/handlers/projects-digest.d.ts +26 -0
- package/src/handlers/prune-stale-data.d.ts +30 -0
- package/src/handlers/registry-stale-inactive-sweep.d.ts +1 -0
- package/src/handlers/scala-tools-currency-watch.d.ts +1 -0
- package/src/handlers/singleton-tools-currency-watch.d.ts +1 -0
- package/src/handlers/sweep-event-log.d.ts +1 -0
- package/src/handlers/sweep-host-cache.d.ts +1 -0
- package/src/handlers/sweep-host-cache.ts +26 -143
- package/src/handlers/upstream-issue-sync.d.ts +1 -0
- package/src/handlers/whatsapp-liveness-logic.d.ts +34 -0
- package/src/handlers/whatsapp-liveness.d.ts +4 -0
- package/src/hono-error-handler.d.ts +25 -0
- package/src/install-harness.d.ts +45 -0
- package/src/install-logging.d.ts +34 -0
- package/src/load-service-install-row.d.ts +24 -0
- package/src/load-service-prisma-client.d.ts +18 -0
- package/src/mount-orpc-router.d.ts +15 -0
- package/src/mount-orpc-router.ts +1 -1
- package/src/mount-server-packages.d.ts +29 -0
- package/src/mount.d.ts +9 -0
- package/src/mutable-app-ref.d.ts +44 -0
- package/src/page-route-shell-types.d.ts +12 -0
- package/src/page-server-orpc/audit-revert-router.d.ts +37 -0
- package/src/page-server-orpc/hub-mcp-client.d.ts +12 -0
- package/src/page-server-orpc/portal-diagnostics-router.d.ts +49 -0
- package/src/page-server-orpc/puck-pages-router.d.ts +58 -0
- package/src/page-server-orpc/server-client.d.ts +16 -0
- package/src/page-server-orpc/templates-router.d.ts +34 -0
- package/src/page-server-orpc/user-preferences-router.d.ts +79 -0
- package/src/pg-boss-handler-registry.d.ts +27 -0
- package/src/pg-boss-mount.d.ts +42 -0
- package/src/proposal-authorized-emails.d.ts +28 -0
- package/src/proposal-chat-inject.d.ts +17 -0
- package/src/proposal-gate.d.ts +18 -0
- package/src/read-html-template.d.ts +38 -0
- package/src/read-viewport-from-request.d.ts +18 -0
- package/src/readiness-gate.d.ts +16 -0
- package/src/register-api-routes.d.ts +18 -0
- package/src/register-crash-handlers.d.ts +15 -0
- package/src/register-page-routes.d.ts +27 -0
- package/src/register-page-routes.ts +3 -0
- package/src/register-page-server-client.d.ts +11 -0
- package/src/register-registry-page-route/ai-chat-prefetch.d.ts +13 -0
- package/src/register-registry-page-route/graph-cache.d.ts +30 -0
- package/src/register-registry-page-route/meta-title.d.ts +2 -0
- package/src/register-registry-page-route/proposal-approval-email-token.d.ts +14 -0
- package/src/register-registry-page-route/proposal-brand-stamping.d.ts +28 -0
- package/src/register-registry-page-route/resolve-page-data.d.ts +1 -0
- package/src/register-registry-page-route.d.ts +27 -0
- package/src/register-static-assets.d.ts +13 -0
- package/src/register-ws-routes.d.ts +28 -0
- package/src/resolve-app-base-url.d.ts +13 -0
- package/src/resolve-brand-favicon.d.ts +28 -0
- package/src/resolve-brand-favicon.ts +1 -1
- package/src/resolve-from-loader-config.d.ts +32 -0
- package/src/resolve-service-context.d.ts +17 -0
- package/src/resolve-service-port.d.ts +75 -0
- package/src/resolve-theme-mode.d.ts +42 -0
- package/src/resolvers/fetch-org-brand.d.ts +2 -0
- package/src/resolvers/portal-route-paths.d.ts +39 -0
- package/src/resolvers/website-by-host-map.d.ts +12 -0
- package/src/resource-chat-inject.d.ts +46 -0
- package/src/resource-not-found.d.ts +60 -0
- package/src/run-boot-hook-list.d.ts +19 -0
- package/src/run-boot-hooks.d.ts +31 -0
- package/src/scala-tools-currency-watchdog.d.ts +10 -0
- package/src/security-headers.d.ts +12 -0
- package/src/serve/serve.d.ts +46 -0
- package/src/serve/stamp-transport-identity.d.ts +32 -0
- package/src/serve/stamp-transport-identity.ts +2 -14
- package/src/server-error-page.d.ts +29 -0
- package/src/start-scheduled-jobs.d.ts +18 -0
- package/src/two-phase-boot.d.ts +136 -0
- package/src/two-phase-boot.ts +17 -85
- package/src/two-phase-health-gate.test.ts +2 -13
- package/src/types.d.ts +46 -0
- package/src/ui-service.d.ts +46 -0
- package/src/ws-boot-self-test.d.ts +19 -0
- package/src/auth-gate.test.ts +0 -70
- package/src/fn-routers/task-sync-lane.test.ts +0 -36
- package/src/handlers/sweep-host-cache.test.ts +0 -133
- package/src/serve/stamp-transport-identity.test.ts +0 -24
- package/src/two-phase-health-gate-client-only.test.ts +0 -77
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Reads the per-service HTML template (codegen'd from
|
|
7
|
+
* `@teamscala/scala-install/src/runtime/index.html`) and substitutes the
|
|
8
|
+
* `{{stylesPath}}` / `{{hydratePath}}` placeholders with the current
|
|
9
|
+
* content-hashed asset URLs from `dist/client/asset-manifest.json`.
|
|
10
|
+
*
|
|
11
|
+
* Build-time produces hashed filenames (e.g. `styles-abc123.css`); the
|
|
12
|
+
* static-asset handler serves them with `cache-control: immutable`. New
|
|
13
|
+
* deployments emit new hashes → new URLs → browsers refetch without ever
|
|
14
|
+
* holding a stale copy. Without the substitution, a fixed
|
|
15
|
+
* `/assets/styles.css` paired with `immutable` would pin every visitor to
|
|
16
|
+
* whatever version they first downloaded.
|
|
17
|
+
*
|
|
18
|
+
* Cached per service process, keyed on the manifest's mtime. The cache MUST
|
|
19
|
+
* invalidate when `asset-manifest.json` changes: `buildClientAssets` can
|
|
20
|
+
* rebuild the bundle AFTER the first request — producing a new content hash,
|
|
21
|
+
* rewriting the manifest, AND deleting the old hashed bundle
|
|
22
|
+
* (`removeStaleHashedFiles`). A lifetime-pinned HTML cache then keeps serving
|
|
23
|
+
* the OLD hash, which now 404s → hydration never runs → the page renders SSR
|
|
24
|
+
* HTML but is dead client-side (the doc-editor overlay never mounts; scala-docs
|
|
25
|
+
* 2026-06-13, exposed by the ui-foundation publish-storm's repeated rebuilds).
|
|
26
|
+
* Re-stat'ing the tiny manifest per request is negligible (OS-cached, no
|
|
27
|
+
* network); the substituted HTML is reused while the mtime is unchanged.
|
|
28
|
+
*
|
|
29
|
+
* NO-SILENT-REVERTS: when the manifest is a TOMBSTONE (the last client build
|
|
30
|
+
* failed), the served HTML is stamped with a `<meta name="x-client-build-status"
|
|
31
|
+
* content="failed">` tag in <head> so any operator / chrome-devtools / theme
|
|
32
|
+
* probe inspecting the page can see the served bundle is STALE (a published fix
|
|
33
|
+
* that reached node_modules but failed to bundle is silently served from the
|
|
34
|
+
* prior bundle otherwise — scala-agents 2026-07-18, the white-on-white toolbar
|
|
35
|
+
* regression that looked "not fixed" while the bundle kept serving pre-fix
|
|
36
|
+
* bytes across restarts).
|
|
37
|
+
*/
|
|
38
|
+
export declare function readHtmlTemplate(): Promise<string>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Server-side viewport detection from request headers. Two tiers:
|
|
7
|
+
* desktop (tablets, laptops, desktops) and mobile (phones).
|
|
8
|
+
*
|
|
9
|
+
* Tablets are NOT a separate tier — CSS handles responsive layout
|
|
10
|
+
* within the desktop tier via flex min/max and @container queries.
|
|
11
|
+
* Only phones (where the layout is fundamentally single-pane) get
|
|
12
|
+
* a different server-rendered page.
|
|
13
|
+
*
|
|
14
|
+
* Order of authority:
|
|
15
|
+
* cookie → Sec-CH-UA-Mobile client hint → User-Agent regex → desktop default.
|
|
16
|
+
*/
|
|
17
|
+
export type ViewportTier = "desktop" | "mobile";
|
|
18
|
+
export declare function readViewportFromRequest(request: Request): ViewportTier;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Readiness gate — returns 503 + Retry-After: 1 until the boot pipeline
|
|
7
|
+
* flips the ready flag true as its last step. Registered as the FIRST
|
|
8
|
+
* middleware in both UI and backend boot paths per readiness-gated-boot.
|
|
9
|
+
*
|
|
10
|
+
* The gate fails open: if it ever throws, the request passes through to
|
|
11
|
+
* the normal handler rather than producing a hard 500.
|
|
12
|
+
*/
|
|
13
|
+
import type { Context, Next } from "hono";
|
|
14
|
+
export declare function isReady(): boolean;
|
|
15
|
+
export declare function setReady(): void;
|
|
16
|
+
export declare function readinessGate(c: Context, next: Next): Promise<Response | void>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Walks `service_install.api_routes[]` and binds each entry to its
|
|
7
|
+
* registered handler (via mountRegistry.handlers, populated by server_packages
|
|
8
|
+
* during their mount phase). Unresolved handler_slug values are fatal — the
|
|
9
|
+
* service cannot boot with declared routes pointing at nothing.
|
|
10
|
+
*
|
|
11
|
+
* Injects x-remote-port header from Bun's getConnInfo so downstream
|
|
12
|
+
* handlers (MCP route) can resolve caller identity via process-tree
|
|
13
|
+
* walking when explicit caller headers are absent.
|
|
14
|
+
*/
|
|
15
|
+
import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
|
|
16
|
+
import type { MountRegistry } from "@teamscala/os/runtime-contracts/mount-context";
|
|
17
|
+
import type { Hono } from "hono";
|
|
18
|
+
export declare function registerApiRoutes(app: Hono, config: ServiceInstallConfig, mountRegistry: MountRegistry, serviceSlug: string): number;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Registers process-level crash handlers (unhandledRejection, uncaughtException)
|
|
7
|
+
* for a given service. Each handler logs the error with the service slug for
|
|
8
|
+
* identification. Per constitution crash-isolation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function registerCrashHandlers(opts: {
|
|
11
|
+
serviceSlug: string;
|
|
12
|
+
}): {
|
|
13
|
+
unhandledRejection: boolean;
|
|
14
|
+
uncaughtException: boolean;
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit directly
|
|
5
|
+
*
|
|
6
|
+
* Page route registration — reads `page_route` registry rows for the
|
|
7
|
+
* current service and delegates to `registerRegistryPageRoute`.
|
|
8
|
+
*
|
|
9
|
+
* All routes are registry-driven (Phase 1): each `page_route` row pairs
|
|
10
|
+
* a URL pattern with a `resource_loader` graph that resolves data in
|
|
11
|
+
* wave-parallel via `resolveFromLoaderConfig`. No deploy-time shell
|
|
12
|
+
* variant routes — the old `service_install.page_routes[]` Phase 2 path
|
|
13
|
+
* was removed when every active service migrated to registry-driven
|
|
14
|
+
* `page_route` rows (all 9 services carry empty arrays).
|
|
15
|
+
*/
|
|
16
|
+
import type { Hono } from "hono";
|
|
17
|
+
import type { ServiceInstallConfig } from "./load-service-install-row.ts";
|
|
18
|
+
import type { RendererComponentEntry } from "./types.ts";
|
|
19
|
+
import type { CreatePageHandler } from "./types.ts";
|
|
20
|
+
export interface RegisterPageRoutesArgs {
|
|
21
|
+
app: Hono;
|
|
22
|
+
config: ServiceInstallConfig;
|
|
23
|
+
components: Record<string, RendererComponentEntry>;
|
|
24
|
+
serviceSlug: string;
|
|
25
|
+
createPageHandler: CreatePageHandler;
|
|
26
|
+
}
|
|
27
|
+
export declare function registerPageRoutes(args: RegisterPageRoutesArgs): Promise<string[]>;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import type { Hono } from "hono";
|
|
18
18
|
import type { ServiceInstallConfig } from "./load-service-install-row.ts";
|
|
19
19
|
import type { RenderModeLiteral } from "./page-route-shell-types.ts";
|
|
20
|
+
import { registerPageServerClient } from "./register-page-server-client.ts";
|
|
20
21
|
import { registerRegistryPageRoute } from "./register-registry-page-route.ts";
|
|
21
22
|
import type { RendererComponentEntry } from "./types.ts";
|
|
22
23
|
import type { CreatePageHandler } from "./types.ts";
|
|
@@ -34,6 +35,8 @@ export async function registerPageRoutes(
|
|
|
34
35
|
): Promise<string[]> {
|
|
35
36
|
const { app, config, components, serviceSlug, createPageHandler } = args;
|
|
36
37
|
|
|
38
|
+
await registerPageServerClient(serviceSlug);
|
|
39
|
+
|
|
37
40
|
const declaredMode = (config as { render_mode?: string }).render_mode;
|
|
38
41
|
const renderMode: RenderModeLiteral =
|
|
39
42
|
declaredMode === "portal"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Registers the system server-client for page-server SSR rendering. Composes
|
|
7
|
+
* the platform-owned routers into the service's generated `appRouter` and
|
|
8
|
+
* stashes the resulting client via setServerClient. Called once at boot before
|
|
9
|
+
* any page route handler fires.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerPageServerClient(serviceSlug: string): Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSR-prefetch the signed-in user's AI-chat conversation so a CLICK-opened AI
|
|
3
|
+
* panel renders the real conversation on first paint — never a loading gate
|
|
4
|
+
* (ssr-then-live-handoff). The AiChat block lives on the /ai PANEL page, which
|
|
5
|
+
* isn't rendered when the panel is closed, so its block serverLoader can't run
|
|
6
|
+
* at page-load; this prefetches server-side (regardless of panel open-state) +
|
|
7
|
+
* stamps pageData.aiChatSession, which AiChat reads on mount. The agent is
|
|
8
|
+
* resolved from the /ai panel page's own AiChat block (single source of truth —
|
|
9
|
+
* no duplicate config to drift). Fired in parallel with the post-processors +
|
|
10
|
+
* bounded by their window; a failure degrades silently to the client-side
|
|
11
|
+
* createOrRestore fallback, never an error page.
|
|
12
|
+
*/
|
|
13
|
+
export declare function prefetchAiChatSession(resolved: Record<string, unknown>, sessionUserId: string | undefined, orgId: string | undefined): Promise<Record<string, unknown> | null>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type GraphCache = {
|
|
2
|
+
getWithMeta(key: string): {
|
|
3
|
+
value: unknown;
|
|
4
|
+
stale: boolean;
|
|
5
|
+
} | undefined;
|
|
6
|
+
refreshInBackground(key: string, compute: () => Promise<unknown>, opts?: {
|
|
7
|
+
ttlMs?: number;
|
|
8
|
+
}): void;
|
|
9
|
+
};
|
|
10
|
+
export declare const staticShellGraphCache: import("@teamscala/cache/cache").ManagedCache<unknown>;
|
|
11
|
+
export declare const paramRouteGraphCache: import("@teamscala/cache/cache").ManagedCache<unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Stale-while-revalidate read of a cached loader graph.
|
|
14
|
+
*
|
|
15
|
+
* - Fresh hit (within ttlMs): return the cached graph, no refresh.
|
|
16
|
+
* - Stale hit (within ttlMs + staleTtlMs): return the cached graph AND fire ONE
|
|
17
|
+
* background refresh via cache.refreshInBackground — single-flight (deduped
|
|
18
|
+
* against the cache's in-flight map), never rejects, leaves the stale entry in
|
|
19
|
+
* place on failure. The caller deep-clones the returned value before use.
|
|
20
|
+
* - Miss (past staleTtlMs, or never cached): return undefined — the caller
|
|
21
|
+
* resolves synchronously and cache.set()s the clone.
|
|
22
|
+
*
|
|
23
|
+
* The refresh callback must return a graph safe to store. To SKIP caching a
|
|
24
|
+
* refresh result (e.g. a primary-missing / dead-link graph that must stay
|
|
25
|
+
* per-request so a just-created record appears immediately), the callback THROWS
|
|
26
|
+
* — refreshInBackground catches it, leaves the stale entry, and logs one line
|
|
27
|
+
* (an observable dead-link signal, not a hard error).
|
|
28
|
+
*/
|
|
29
|
+
export declare function readCachedGraph(cache: GraphCache, key: string, refresh: () => Promise<unknown>): unknown | undefined;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare function buildMetaFromContext(metaContext: Record<string, Record<string, string>> | undefined, resolved: Record<string, unknown>): Record<string, unknown>;
|
|
2
|
+
export declare function resolveTemplateTitle(service: string, resolved: Record<string, unknown>): string | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the `{{approvalEmail}}` placeholder in a proposal page's button props
|
|
3
|
+
* (e.g. the Approve CTA) in place. Routing: the organisation's registered
|
|
4
|
+
* company email FIRST (organisation_profile.contact_email, already resolved
|
|
5
|
+
* into pageData.brand.contactEmail); the proposal creator's user email only as
|
|
6
|
+
* a fallback when the org has none — so a cloned proposal can never carry a
|
|
7
|
+
* stale hard-coded address. The creator lookup is null-safe and adds no user
|
|
8
|
+
* exposure: it resolves only where the in-process ORPC client exposes `user`
|
|
9
|
+
* (super-admin), degrading to the org email elsewhere. Called only for proposal
|
|
10
|
+
* pages; other registry pages are untouched.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyApprovalEmailToken(pageData: Record<string, unknown>, proposal: {
|
|
13
|
+
created_by?: string;
|
|
14
|
+
} | undefined): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stash a proposal's connected partner logo/colour on `pageData` from the
|
|
3
|
+
* `partnerOrg` loader source (`crm_organisations` loaded by the proposal's
|
|
4
|
+
* `partner_id`, via the SAME proven ORPC loader path that loads the proposal
|
|
5
|
+
* itself). resolve-page-brand (service-runtime) prefers `pageData.partnerLogo`
|
|
6
|
+
* over any inline body-Hero prop and fans it into the shared `/header` so a
|
|
7
|
+
* `source: "partner"` OrgLogo fills automatically — the partner logo lives on
|
|
8
|
+
* the CRM record, never duplicated per proposal. No partner → partnerOrg is
|
|
9
|
+
* null → nothing set → the partner logo block renders blank. Reading the
|
|
10
|
+
* already-resolved loader value (not a second hand-rolled getServerClient call)
|
|
11
|
+
* is what makes this reliable: the earlier direct `crm_organisations.findFirst`
|
|
12
|
+
* with a Prisma-style object `select` resolved intermittently/never against the
|
|
13
|
+
* generated ORPC, leaving the header partner blank (2026-06-12).
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyPartnerBrand(pageData: Record<string, unknown>, partnerOrg: {
|
|
16
|
+
logo_url?: string | null;
|
|
17
|
+
brand_primary_color?: string | null;
|
|
18
|
+
} | undefined | null): void;
|
|
19
|
+
/**
|
|
20
|
+
* Auto-resolve the client's company logo from the `clientOrg` loader source
|
|
21
|
+
* (contact_id → company_id → crm_organisations.logo_url). Injects into the
|
|
22
|
+
* Hero block's clientLogo prop when it's empty — no manual logo URL needed
|
|
23
|
+
* per proposal when the contact's company profile has a logo.
|
|
24
|
+
*/
|
|
25
|
+
export declare function applyClientLogo(pageData: Record<string, unknown>, clientOrg: {
|
|
26
|
+
logo_url?: string | null;
|
|
27
|
+
name?: string | null;
|
|
28
|
+
} | undefined | null): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolvedToPageData(resolved: Record<string, unknown>, meta: Record<string, unknown>, request: Request, providers: string[] | undefined, regionSlots?: string[], themeOverride?: "light" | "dark"): Record<string, unknown>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Reads `page_route` registry rows for the current service slug and registers
|
|
7
|
+
* Hono routes that resolve data via `resource_loader` graph-based sources.
|
|
8
|
+
*
|
|
9
|
+
* Every page_route uses pattern `/:id` and every resource_loader uses a
|
|
10
|
+
* `sources[]` dependency graph. When header/footer sources are present in
|
|
11
|
+
* the resolved graph, the output includes a `regions` array for shell rendering.
|
|
12
|
+
* Otherwise it produces single-page puckData.
|
|
13
|
+
*/
|
|
14
|
+
import type { RenderModeLiteral } from "./page-route-shell-types.ts";
|
|
15
|
+
import type { Hono } from "hono";
|
|
16
|
+
import type { CreatePageHandler, RendererComponentEntry } from "./types.ts";
|
|
17
|
+
interface RegistryRouteArgs {
|
|
18
|
+
app: Hono;
|
|
19
|
+
serviceSlug: string;
|
|
20
|
+
components: Record<string, RendererComponentEntry>;
|
|
21
|
+
createPageHandler: CreatePageHandler;
|
|
22
|
+
authMode: string;
|
|
23
|
+
renderMode: RenderModeLiteral;
|
|
24
|
+
providers: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function registerRegistryPageRoute(args: RegistryRouteArgs): Promise<string[]>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-handlers
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Routes every service exposes regardless of registry config: /health
|
|
7
|
+
* (liveness probe), /favicon.svg (default brand mark), /api/assets/*
|
|
8
|
+
* (S3 asset proxy), and /assets/* (client bundle + stylesheet produced
|
|
9
|
+
* by build-client-assets.ts into dist/client/, served identically in
|
|
10
|
+
* dev and prod per scala-os/ui.md `bun-build-only`).
|
|
11
|
+
*/
|
|
12
|
+
import type { Hono } from "hono";
|
|
13
|
+
export declare function registerStaticAssets(app: Hono, serviceSlug: string, brandOrg?: string): Promise<void>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Registers each `service_install.websocket_routes[]` entry as a Hono WS route.
|
|
7
|
+
* The ORPC WS (`/api/rpc/ws`) is mounted by the canonical primitive
|
|
8
|
+
* `mountOrpcWs` (@teamscala/orpc/ws/server) over the FULLY-COMPOSED app router —
|
|
9
|
+
* this runs AFTER registerFnRouters, so the eager RPCHandler snapshots every
|
|
10
|
+
* namespace, and any `output: eventIterator(...)` procedure dispatches (rule
|
|
11
|
+
* `orpc-ws-via-mount-orpc-ws`). It is NOT a server_package WsRouteHandler — it
|
|
12
|
+
* is exempt from the legacy invariant below.
|
|
13
|
+
*
|
|
14
|
+
* The legacy `WsRouteHandler` interface (open/message/close on a Bun
|
|
15
|
+
* ServerWebSocket) is preserved by an adapter for already-migrated
|
|
16
|
+
* server_packages (terminal-ws); new WS handlers should use Hono's WSEvents
|
|
17
|
+
* directly.
|
|
18
|
+
*
|
|
19
|
+
* INVARIANT (legacy paths only): every handler registered via
|
|
20
|
+
* mountCtx.registerWsRoute MUST have a matching entry in config.websocket_routes
|
|
21
|
+
* (minus /api/rpc/ws), and vice versa. A mismatch is fatal — the service cannot
|
|
22
|
+
* boot with handlers declared but never wired (silent 404 on upgrade), or routes
|
|
23
|
+
* that point at handlers no server_package registered.
|
|
24
|
+
*/
|
|
25
|
+
import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
|
|
26
|
+
import type { MountRegistry } from "@teamscala/os/runtime-contracts/mount-context";
|
|
27
|
+
import type { Hono } from "hono";
|
|
28
|
+
export declare function registerWsRoutes(app: Hono, config: ServiceInstallConfig, mountRegistry: MountRegistry, serviceSlug: string): Promise<number>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* The scala-projects app's own public hostname, read from its `service_install`
|
|
7
|
+
* row rather than hardcoded — a service knows its own deploy config only
|
|
8
|
+
* through the registry (registries-are-source-of-truth), never a literal in
|
|
9
|
+
* source. Cached for the process lifetime: this changes only on a redeploy,
|
|
10
|
+
* not per-request. Shared by every server-side path that builds a link back
|
|
11
|
+
* into the app (approval emails, mention emails, digest emails).
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveAppBaseUrl(): Promise<string>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Host-level brand favicon resolution. A single-brand UI service's host has
|
|
7
|
+
* a KNOWN owner (`service_install.config.brand_org`) — serving the generic
|
|
8
|
+
* platform "S" at the implicit /favicon.ico path there is a defect: browsers
|
|
9
|
+
* and crawlers hit that path directly and their favicon caches are sticky
|
|
10
|
+
* (operator-observed 2026-06-05 on launcha's proposal pages).
|
|
11
|
+
*
|
|
12
|
+
* Resolution is by convention from the org's asset store:
|
|
13
|
+
* `<brand_org>/logos/favicon.png`. Existence is probed once through the
|
|
14
|
+
* in-process asset proxy and cached (positive AND negative) so the favicon
|
|
15
|
+
* route never adds S3 latency per request. No brand_org, or no asset at the
|
|
16
|
+
* convention key → null, and the caller serves the generic fallback.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveBrandFaviconPath(brandOrg: string | undefined): Promise<string | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Eagerly resolve + cache the favicon at boot so the FIRST user request never
|
|
21
|
+
* pays the S3 existence-probe latency. The probe (`handleAssetProxy` →
|
|
22
|
+
* `s3.file(key).stat()`) is a COLD S3 call — the connection setup to the asset
|
|
23
|
+
* store takes 6s locally / up to 21s in prod, and the in-process cache resets
|
|
24
|
+
* on every restart (deploys, package-poller). Without warming, the first
|
|
25
|
+
* /favicon.ico after each restart stalls for that duration (operator-reported
|
|
26
|
+
* 23s). Fire-and-forget — runs in the background during boot, never blocks.
|
|
27
|
+
*/
|
|
28
|
+
export declare function warmBrandFaviconCache(brandOrg: string | undefined): void;
|
|
@@ -55,7 +55,7 @@ export async function resolveBrandFaviconPath(
|
|
|
55
55
|
* pays the S3 existence-probe latency. The probe (`handleAssetProxy` →
|
|
56
56
|
* `s3.file(key).stat()`) is a COLD S3 call — the connection setup to the asset
|
|
57
57
|
* store takes 6s locally / up to 21s in prod, and the in-process cache resets
|
|
58
|
-
* on every restart (deploys,
|
|
58
|
+
* on every restart (deploys, package-poller). Without warming, the first
|
|
59
59
|
* /favicon.ico after each restart stalls for that duration (operator-reported
|
|
60
60
|
* 23s). Fire-and-forget — runs in the background during boot, never blocks.
|
|
61
61
|
*/
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Walks a resource_loader.sources[] graph and resolves each source in
|
|
7
|
+
* dependency order. Returns an assembled data object keyed by source key.
|
|
8
|
+
*
|
|
9
|
+
* Source types: "orpc", "prisma", "resolver_fn".
|
|
10
|
+
* Each source may declare depends_on for ordering.
|
|
11
|
+
* Params are interpolated from URL params and prior resolved data.
|
|
12
|
+
*/
|
|
13
|
+
interface SourceConfig {
|
|
14
|
+
key: string;
|
|
15
|
+
source: "orpc" | "prisma" | "resolver_fn";
|
|
16
|
+
model?: string;
|
|
17
|
+
where?: Record<string, unknown>;
|
|
18
|
+
select?: string[];
|
|
19
|
+
depends_on?: string[];
|
|
20
|
+
optional?: boolean;
|
|
21
|
+
resolver_fn?: string;
|
|
22
|
+
resolver_input?: Record<string, unknown>;
|
|
23
|
+
registry_filter?: Record<string, unknown>;
|
|
24
|
+
transforms?: string[];
|
|
25
|
+
}
|
|
26
|
+
interface LoaderConfig {
|
|
27
|
+
sources: SourceConfig[];
|
|
28
|
+
merge_strategy?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export declare function resolveFromLoaderConfig(config: LoaderConfig, params: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
31
|
+
export declare function interpolateMeta(template: Record<string, unknown>, data: Record<string, unknown>): Record<string, unknown>;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Resolves the minimum service context the bin needs to choose its launch
|
|
7
|
+
* path: connect DB, load registry, read service_install row, return kind +
|
|
8
|
+
* port. Caller is responsible for disconnecting.
|
|
9
|
+
*/
|
|
10
|
+
import type { ServiceInstallConfig } from "@teamscala/db/registry/loader/load-service-install-row";
|
|
11
|
+
export interface ServiceContext {
|
|
12
|
+
kind: "ui" | "backend" | "runtime";
|
|
13
|
+
port: number;
|
|
14
|
+
config: ServiceInstallConfig;
|
|
15
|
+
disconnect: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export declare function resolveServiceContext(serviceSlug: string, databaseUrl: string): Promise<ServiceContext>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-runtime
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Decides which port a service binds, from the two sources that can claim one:
|
|
7
|
+
* the PORT environment variable and the port the registry declares for that
|
|
8
|
+
* service (`service_routing.local_port` for a backend, `service_install.port`
|
|
9
|
+
* for a UI service).
|
|
10
|
+
*
|
|
11
|
+
* This is the resolver for BOTH kinds. It lives in service-boot rather than
|
|
12
|
+
* service-runtime because the UI branch boots from here and may not import
|
|
13
|
+
* upward; keeping one copy is the point, since the second copy is exactly what
|
|
14
|
+
* went wrong (below).
|
|
15
|
+
*
|
|
16
|
+
* The rule, and why it is not "env always wins":
|
|
17
|
+
*
|
|
18
|
+
* Railway injects PORT and healthchecks that exact port, reporting the service
|
|
19
|
+
* unavailable if it listens elsewhere. So on Railway the injected PORT is
|
|
20
|
+
* authoritative period — a `service_install`/`service_routing` row carries the
|
|
21
|
+
* service's LOCAL dev port (e.g. portal 5200), and on Railway binding that
|
|
22
|
+
* instead of PORT means Railway probes a port nothing listens on and the deploy
|
|
23
|
+
* fails healthcheck. The registry-wins rule below exists to stop LOCAL port
|
|
24
|
+
* collisions (one host, many services); inside a Railway container there are no
|
|
25
|
+
* sibling services to collide with, so PORT must win. `isRailway` is the caller's
|
|
26
|
+
* assertion that it is booting inside Railway (from `RAILWAY_ENVIRONMENT`).
|
|
27
|
+
*
|
|
28
|
+
* Off Railway, a `service_routing`/`service_install` row means this service has
|
|
29
|
+
* a DECLARED port on this host. There, a PORT that disagrees is a polluted launcher environment, never
|
|
30
|
+
* an intention, and obeying it binds someone else's port. That is not a
|
|
31
|
+
* hypothetical: mcp-ai-chat-system booted onto 3021 — session-picker's port —
|
|
32
|
+
* while its own MCP_SERVER_URL still pointed at 8080. Both processes bound 3021,
|
|
33
|
+
* SO_REUSEPORT load-balanced between them, and every symptom appeared somewhere
|
|
34
|
+
* else: a chat loopback that resolved to nothing, tool-description curation
|
|
35
|
+
* failing for every tool and disabling itself via its circuit breaker, RPC calls
|
|
36
|
+
* landing in the wrong service, and 59 restarts. Nothing failed loudly because
|
|
37
|
+
* nothing ever compared the two numbers.
|
|
38
|
+
*
|
|
39
|
+
* So the registry wins wherever it has an opinion, and the disagreement is
|
|
40
|
+
* REPORTED rather than silently resolved — a wrong port that boots cleanly is
|
|
41
|
+
* the expensive shape, because the damage surfaces far from the cause.
|
|
42
|
+
*
|
|
43
|
+
* That reasoning was written for the backend branch and wired only there, so
|
|
44
|
+
* the UI branch kept taking `process.env.PORT` unconditionally — and on
|
|
45
|
+
* 2026-07-31 reproduced the identical incident in the other direction:
|
|
46
|
+
* scala-quiz (kind:ui, declared 5203) inherited a leaked PORT=3021 from the
|
|
47
|
+
* tmux server environment and bound session-picker's port. Half of every RPC
|
|
48
|
+
* to the session daemon was answered by a quiz service with no /rpc routes.
|
|
49
|
+
*
|
|
50
|
+
* A rule enforced on one of two branches is not enforced. Both branches now
|
|
51
|
+
* call this function, and `port-resolution-is-registry-authoritative` fails
|
|
52
|
+
* the push if either stops.
|
|
53
|
+
*/
|
|
54
|
+
export interface PortResolution {
|
|
55
|
+
/** The port to bind. */
|
|
56
|
+
port: number;
|
|
57
|
+
/** Which source decided it — for the boot log, so the choice is auditable. */
|
|
58
|
+
source: "env" | "service_routing";
|
|
59
|
+
/** Set when PORT disagreed with the row; the caller logs it. */
|
|
60
|
+
conflict?: {
|
|
61
|
+
envPort: number;
|
|
62
|
+
declaredPort: number;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* @param envPort parsed PORT env, or undefined when unset/unparseable
|
|
67
|
+
* @param declaredPort `service_routing.local_port`, or undefined when the
|
|
68
|
+
* service has no routing row (the Railway-only case)
|
|
69
|
+
* @param isRailway true when booting inside Railway (caller derives from
|
|
70
|
+
* `RAILWAY_ENVIRONMENT`); there the injected PORT is authoritative
|
|
71
|
+
* regardless of the row, because Railway healthchecks it
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveServicePort(envPort: number | undefined, declaredPort: number | undefined, isRailway?: boolean): PortResolution | {
|
|
74
|
+
error: "no-port-source";
|
|
75
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* The ONE theme-mode decider for every page render, across every UI app
|
|
7
|
+
* (portal, proposals, sign, forms, quiz, gpt, public). Every route handler in service-boot
|
|
8
|
+
* resolves light/dark through this single function — never its own inline
|
|
9
|
+
* `(... === "dark" ? "dark" : "light")` chain. That inline form was duplicated
|
|
10
|
+
* across every route branch, so flipping one app's theme required editing
|
|
11
|
+
* every copy (the heavy lifting this removes).
|
|
12
|
+
*
|
|
13
|
+
* Resolution order (first match wins):
|
|
14
|
+
* 1. ?theme=light|dark on the URL — the embed override. Set when a page is
|
|
15
|
+
* framed inside another shell (a form in a GPT tool panel) so the framed
|
|
16
|
+
* page matches the host's scheme. Also marks the render `embedded` so the
|
|
17
|
+
* page can drop its own brand page-surface and render the host's clean
|
|
18
|
+
* surface.
|
|
19
|
+
* 2. candidates[] — page rootProps.theme, brand theme, website default, in
|
|
20
|
+
* whatever priority the caller passes them.
|
|
21
|
+
* 3. "light".
|
|
22
|
+
*
|
|
23
|
+
* Pairs with the brand/token decider (resolveBrand → computeThemeVars,
|
|
24
|
+
* `unified-brand-theme`): that owns the SURFACE, this owns the MODE.
|
|
25
|
+
*/
|
|
26
|
+
export interface ResolvedThemeMode {
|
|
27
|
+
theme: "light" | "dark";
|
|
28
|
+
/** True iff the mode came from the ?theme= embed override. */
|
|
29
|
+
embedded: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function resolveThemeMode(opts: {
|
|
32
|
+
/** Request URL (or its href) — read for the ?theme= embed override. */
|
|
33
|
+
url?: URL | string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Explicit embed override, when the caller already extracted ?theme= from
|
|
36
|
+
* the URL upstream (e.g. a pure data-shaper that has no Request). Same
|
|
37
|
+
* effect and priority as the URL form.
|
|
38
|
+
*/
|
|
39
|
+
override?: unknown;
|
|
40
|
+
/** Theme candidates in priority order: rootTheme, brandTheme, websiteTheme. */
|
|
41
|
+
candidates?: Array<unknown>;
|
|
42
|
+
}): ResolvedThemeMode;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* portal-route-paths.ts — pure path mapper for portal region routing.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from resolve-from-loader-config.ts (the hardcoded
|
|
9
|
+
* `resolvePortalRoutePaths` arm) so it can be invoked via a resolver_fn
|
|
10
|
+
* registry row instead of an if-chain. Faithful extraction — no logic change.
|
|
11
|
+
* The request path is the center page's builda path ("/" falls back to
|
|
12
|
+
* home_path), and the per-route toolbar page follows the flat naming convention
|
|
13
|
+
* `/<last-segment>-toolbar`. Pages without a matching toolbar page simply
|
|
14
|
+
* resolve an empty toolbar region (the source is declared optional).
|
|
15
|
+
*/
|
|
16
|
+
export interface ResolverArgs {
|
|
17
|
+
input: Record<string, unknown>;
|
|
18
|
+
client: unknown;
|
|
19
|
+
params: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Extract the organisation id from a portal-shaped callback/path — the single
|
|
23
|
+
* authority for "which segment is the org" so the sign-in brand resolver and the
|
|
24
|
+
* route resolver cannot drift on the answer (portal-branding-resolves-page-org-first).
|
|
25
|
+
*
|
|
26
|
+
* The canonical portal shape is `/<websiteId>/<orgId>/<path>` — the org is
|
|
27
|
+
* segment 1, NOT segment 0. Reading segment 0 fed the WEBSITE id into brand
|
|
28
|
+
* resolution, which resolved no org and fell back to the super-admin org — the
|
|
29
|
+
* exact cross-tenant mis-branding this exists to prevent (bind-page-handler.ts,
|
|
30
|
+
* found 2026-07-21).
|
|
31
|
+
*
|
|
32
|
+
* Single-UUID hosts keep segment 0 unchanged: scala-projects routes `/<orgId>/…`
|
|
33
|
+
* and proposal/form/quiz hosts route `/<resourceId>/…` (a non-org first segment
|
|
34
|
+
* that resolves to no brand and falls back as before — no regression, only the
|
|
35
|
+
* correct-org improvement for the two-UUID portal shape). Returns undefined when
|
|
36
|
+
* neither leading segment is a UUID.
|
|
37
|
+
*/
|
|
38
|
+
export declare function extractOrgIdFromCallbackPath(callbackUrl: string): string | undefined;
|
|
39
|
+
export declare function resolvePortalRoutePaths({ input, params }: ResolverArgs): Record<string, unknown>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system ui-service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* website-by-host-map.ts — resolves the website + body path for a host.
|
|
7
|
+
*
|
|
8
|
+
* Extracted from resolve-from-loader-config.ts (the hardcoded
|
|
9
|
+
* `resolveWebsiteByHostMap` arm). Faithful extraction — no logic change.
|
|
10
|
+
*/
|
|
11
|
+
import type { ResolverArgs } from "./portal-route-paths.ts";
|
|
12
|
+
export declare function resolveWebsiteByHostMap({ input, client, params }: ResolverArgs): Promise<Record<string, unknown> | null>;
|