Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb28de4d5e | ||
|
|
eb754d12c3 |
@@ -80,6 +80,12 @@ tasks:
|
||||
OPEN: '{{.OPEN | default ""}}'
|
||||
env:
|
||||
BACKEND_URL: '{{.BACKEND_URL}}'
|
||||
# Dev-only browser-tab label so concurrent worktrees are distinguishable.
|
||||
# Only the worktree folder basename (e.g. "wt1") is exposed — never the
|
||||
# full path, hostname, or user. Consumed at dev-serve time by vite.config
|
||||
# and dropped from production builds.
|
||||
STIRLING_DEV_LABEL:
|
||||
sh: basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||
cmds:
|
||||
- npx vite editor --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
|
||||
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
* tool list unchanged.
|
||||
*/
|
||||
|
||||
import { useMemo, type ReactNode } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
/** Whether the right rail should host the Policies section. False in core. */
|
||||
export function usePoliciesEnabled(): boolean {
|
||||
return useMemo(() => false, []);
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Whether the Policies list should appear for the current user. False in core. */
|
||||
export function usePoliciesVisible(_enabled = false): boolean {
|
||||
export function usePoliciesVisible(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function RightSidebar() {
|
||||
} = useToolWorkflow();
|
||||
|
||||
const policiesEnabled = usePoliciesEnabled();
|
||||
const policiesVisible = usePoliciesVisible(policiesEnabled);
|
||||
const policiesVisible = usePoliciesVisible();
|
||||
const rawPolicyDetailActive = usePolicyDetailActive();
|
||||
const fullscreenExpanded = useIsFullscreenExpanded();
|
||||
const fullscreenGeometry = useToolPanelGeometry({
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Prefixes the browser tab title with the current worktree name during local
|
||||
* development so multiple concurrently-running worktrees (e.g. wt1, wt2, spdf1)
|
||||
* are distinguishable at a glance instead of all showing "Stirling PDF".
|
||||
*
|
||||
* The label is injected as a build-time constant by vite.config, sourced from
|
||||
* the top-level dev tasks. It is an empty string in production builds, so this
|
||||
* whole feature compiles away to a no-op outside `vite` dev-serve.
|
||||
*/
|
||||
|
||||
const LABEL =
|
||||
typeof __DEV_WORKTREE_LABEL__ === "string" ? __DEV_WORKTREE_LABEL__ : "";
|
||||
|
||||
export function applyDevWorktreeLabel(): void {
|
||||
if (!LABEL || typeof document === "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
const prefix = `[${LABEL}] `;
|
||||
|
||||
const ensurePrefixed = () => {
|
||||
if (!document.title.startsWith(prefix)) {
|
||||
// The app rewrites document.title on route/tool changes; re-apply the
|
||||
// prefix on top of whatever the app just set.
|
||||
document.title = prefix + document.title;
|
||||
}
|
||||
};
|
||||
|
||||
ensurePrefixed();
|
||||
|
||||
const titleEl = document.querySelector("title");
|
||||
if (titleEl) {
|
||||
new MutationObserver(ensurePrefixed).observe(titleEl, {
|
||||
childList: true,
|
||||
characterData: true,
|
||||
subtree: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,12 @@ import { BrowserRouter } from "react-router-dom";
|
||||
import App from "@app/App";
|
||||
import "@app/i18n"; // Initialize i18next
|
||||
import { BASE_PATH } from "@app/constants/app";
|
||||
import { applyDevWorktreeLabel } from "@app/utils/applyDevWorktreeLabel";
|
||||
|
||||
import { startEagerWasmCompilation } from "@app/services/wasmPrecompiler";
|
||||
|
||||
applyDevWorktreeLabel();
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
const scheduleCompilation = () => {
|
||||
if (typeof requestIdleCallback === "function") {
|
||||
|
||||
@@ -90,10 +90,10 @@ export function usePoliciesEnabled(): boolean {
|
||||
/**
|
||||
* Whether the right rail should show the Policies section.
|
||||
*/
|
||||
export function usePoliciesVisible(enabled = POLICIES_ENABLED): boolean {
|
||||
const pol = usePolicies(enabled);
|
||||
export function usePoliciesVisible(): boolean {
|
||||
const pol = usePolicies();
|
||||
const { categories } = usePolicyCatalog();
|
||||
if (!enabled) return false;
|
||||
if (!POLICIES_ENABLED) return false;
|
||||
return pol.canConfigure || categories.some((c) => !c.comingSoon);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ if (typeof globalThis.crypto?.randomUUID !== "function") {
|
||||
const api = vi.hoisted(() => ({
|
||||
store: new Map<string, { id: string }>(),
|
||||
seq: 0,
|
||||
}));
|
||||
vi.mock("@app/services/policyApi", () => ({
|
||||
listPolicies: vi.fn(async () => [...api.store.values()]),
|
||||
savePolicy: vi.fn(async (p: { id?: string }) => {
|
||||
const id = p.id && p.id.length > 0 ? p.id : `be-${++api.seq}`;
|
||||
@@ -33,15 +35,6 @@ const api = vi.hoisted(() => ({
|
||||
runPolicyPipeline: vi.fn(),
|
||||
getPolicyRun: vi.fn(),
|
||||
}));
|
||||
vi.mock("@app/services/policyApi", () => ({
|
||||
listPolicies: api.listPolicies,
|
||||
savePolicy: api.savePolicy,
|
||||
getPolicy: api.getPolicy,
|
||||
deletePolicy: api.deletePolicy,
|
||||
runStoredPolicy: api.runStoredPolicy,
|
||||
runPolicyPipeline: api.runPolicyPipeline,
|
||||
getPolicyRun: api.getPolicyRun,
|
||||
}));
|
||||
|
||||
import { usePolicies } from "@app/hooks/usePolicies";
|
||||
|
||||
@@ -72,7 +65,6 @@ const wizardResult = {
|
||||
|
||||
describe("usePolicies", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
localStorage.clear();
|
||||
api.store.clear();
|
||||
api.seq = 0;
|
||||
@@ -86,12 +78,6 @@ describe("usePolicies", () => {
|
||||
expect(result.current.policies.security.configured).toBe(false);
|
||||
});
|
||||
|
||||
it("skips the backend reconcile when disabled", async () => {
|
||||
renderHook(() => usePolicies(false));
|
||||
await act(async () => {});
|
||||
expect(api.listPolicies).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("enabling a policy persists it to the backend + marks it configured", async () => {
|
||||
const { result } = renderHook(() => usePolicies());
|
||||
await act(async () => {
|
||||
|
||||
@@ -73,7 +73,7 @@ function toStoreRequest(
|
||||
};
|
||||
}
|
||||
|
||||
export function usePolicies(enabled = true) {
|
||||
export function usePolicies() {
|
||||
const [policies, setPolicies] = useState<PoliciesByCategory>(loadPolicies);
|
||||
const { config, refetch: refetchAppConfig } = useAppConfig();
|
||||
const { isTeamLeader } = useSaaSTeam();
|
||||
@@ -88,7 +88,6 @@ export function usePolicies(enabled = true) {
|
||||
// locally-cached folderId; retry with backoff since the backend may not be up yet.
|
||||
// On recovery, also re-resolve app config in case its admin/team-leader flags settled false while down.
|
||||
useEffect(() => {
|
||||
if (!enabled) return;
|
||||
let cancelled = false;
|
||||
let attempt = 0;
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
@@ -128,7 +127,7 @@ export function usePolicies(enabled = true) {
|
||||
cancelled = true;
|
||||
if (timer) clearTimeout(timer);
|
||||
};
|
||||
}, [enabled]);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Enable a new policy from the wizard result: persist it to the backend (the
|
||||
|
||||
Vendored
+7
@@ -29,3 +29,10 @@ interface ImportMetaEnv {
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev-only worktree folder basename injected by vite.config at dev-serve time
|
||||
* (empty string in production builds). Used to prefix the browser tab title so
|
||||
* concurrent worktrees are distinguishable.
|
||||
*/
|
||||
declare const __DEV_WORKTREE_LABEL__: string;
|
||||
|
||||
@@ -182,7 +182,13 @@ const TSCONFIG_MAP: Record<BuildMode, string> = {
|
||||
prototypes: "./tsconfig.prototypes.vite.json",
|
||||
};
|
||||
|
||||
export default defineConfig(async ({ mode }) => {
|
||||
export default defineConfig(async ({ mode, command }) => {
|
||||
// Dev-only browser-tab label (worktree folder basename) surfaced by the
|
||||
// top-level dev tasks so concurrent worktrees have distinguishable tabs.
|
||||
// Only injected during `vite` (dev serve) — never baked into a production
|
||||
// build — and carries only the folder name, no path/host/user info.
|
||||
const devWorktreeLabel =
|
||||
command === "serve" ? (process.env.STIRLING_DEV_LABEL ?? "") : "";
|
||||
// Load env files relative to this config (frontend/editor/), regardless of
|
||||
// where the build was invoked from. The previous `process.cwd()` worked when
|
||||
// this file lived at frontend/, but after the editor was moved under
|
||||
@@ -250,6 +256,9 @@ export default defineConfig(async ({ mode }) => {
|
||||
};
|
||||
|
||||
return {
|
||||
define: {
|
||||
__DEV_WORKTREE_LABEL__: JSON.stringify(devWorktreeLabel),
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
...(runSubpath ? [subpathBareRedirectPlugin(runSubpath)] : []),
|
||||
|
||||
Reference in New Issue
Block a user