Skip to content

Commit e24e857

Browse files
committed
fix(webapp): drop the project scope from tab titles
1 parent 7acd1c4 commit e24e857

3 files changed

Lines changed: 15 additions & 74 deletions

File tree

.server-changes/page-tab-titles.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ area: webapp
33
type: improvement
44
---
55

6-
Browser tabs now show the page you're on, plus the project and environment, so several open dashboard tabs are easy to tell apart.
6+
Browser tabs now name the page you're on, and the run, task or queue when you're looking at one, instead of all reading "Trigger.dev".

apps/webapp/app/utils/pageTitle.test.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,14 @@ const orgRoute: Route = {
4747
id: ORGANIZATION_MATCH_ID,
4848
data: {
4949
organization: { title: "Acme" },
50-
project: { name: "my-project" },
51-
environment: { type: "PRODUCTION" },
5250
},
5351
};
5452

5553
describe("pageMeta", () => {
5654
it("adds the project and environment scope to an environment page", () => {
5755
const title = renderTitle([rootRoute, orgRoute, { id: "routes/runs", meta: pageMeta("Runs") }]);
5856

59-
expect(title).toBe("Runs | my-project (Prod) | Trigger.dev");
57+
expect(title).toBe("Runs | Trigger.dev");
6058
});
6159

6260
it("uses the deepest declared title", () => {
@@ -67,7 +65,7 @@ describe("pageMeta", () => {
6765
{ id: "routes/runs.$runParam", meta: pageMeta(["run_abc", "Runs"]) },
6866
]);
6967

70-
expect(title).toBe("run_abc | Runs | my-project (Prod) | Trigger.dev");
68+
expect(title).toBe("run_abc | Runs | Trigger.dev");
7169
});
7270

7371
it("falls back to the nearest declared title, then the app title", () => {
@@ -78,7 +76,7 @@ describe("pageMeta", () => {
7876
{ id: "routes/runs", meta: pageMeta("Runs") },
7977
{ id: "routes/runs._index" },
8078
])
81-
).toBe("Runs | my-project (Prod) | Trigger.dev");
79+
).toBe("Runs | Trigger.dev");
8280

8381
expect(renderTitle([rootRoute, orgRoute, { id: "routes/runs._index" }])).toBe("Trigger.dev");
8482
});
@@ -95,17 +93,17 @@ describe("pageMeta", () => {
9593
orgRoute,
9694
{ id: "routes/task", data: { task: { slug: "my-task" } }, meta },
9795
])
98-
).toBe("my-task | Tasks | my-project (Prod) | Trigger.dev");
96+
).toBe("my-task | Tasks | Trigger.dev");
9997

10098
expect(
10199
renderTitle([rootRoute, orgRoute, { id: "routes/task", meta }], {
102100
...ENV_PARAMS,
103101
taskParam: "other",
104102
})
105-
).toBe("other | Tasks | my-project (Prod) | Trigger.dev");
103+
).toBe("other | Tasks | Trigger.dev");
106104
});
107105

108-
it("keeps the org name for org-scoped pages and the app env tag", () => {
106+
it("keeps the app env tag", () => {
109107
const stagingRoot: Route = {
110108
id: "root",
111109
data: { appEnv: "staging" },
@@ -121,20 +119,7 @@ describe("pageMeta", () => {
121119
{ organizationSlug: "acme" }
122120
);
123121

124-
expect(title).toBe("Team | Acme | Trigger.dev (staging)");
125-
});
126-
127-
it("ignores the project the org loader resolved when the URL names none", () => {
128-
// The org loader always resolves a "best" project, so an org page would otherwise
129-
// advertise a project the user is not looking at.
130-
const title = renderTitle(
131-
[rootRoute, orgRoute, { id: "routes/team", meta: pageMeta("Team") }],
132-
{
133-
organizationSlug: "acme",
134-
}
135-
);
136-
137-
expect(title).toBe("Team | Acme | Trigger.dev");
122+
expect(title).toBe("Team | Trigger.dev (staging)");
138123
});
139124

140125
it("carries the root's non-title tags through", () => {
@@ -150,6 +135,6 @@ describe("pageMeta", () => {
150135
} as any) as MetaDescriptor[];
151136

152137
expect(descriptors).toContainEqual({ name: "viewport", content: "width=1024" });
153-
expect(renderTitle(routes)).toBe("Runs | my-project (Prod) | Trigger.dev");
138+
expect(renderTitle(routes)).toBe("Runs | Trigger.dev");
154139
});
155140
});

apps/webapp/app/utils/pageTitle.ts

Lines changed: 6 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@ import { appEnvTitleTag } from "~/utils";
44

55
/**
66
* Tab titles. Every page exports `export const meta = pageMeta(...)` and only says what the page
7-
* is; the shared scope suffix is added here.
7+
* is; the app title is added here.
88
*
99
* Shape (leading words carry the information, so a narrow tab still reads):
10-
* env-scoped page: `Runs | my-project (Prod) | Trigger.dev`
11-
* env-scoped entity: `run_abc | Runs | my-project (Prod) | Trigger.dev`
12-
* org-scoped page: `Team | Acme | Trigger.dev`
13-
* everything else: `Login to Trigger.dev` etc. (no scope to add)
10+
* page: `Runs | Trigger.dev`
11+
* entity: `run_abc | Runs | Trigger.dev`
12+
* else: `Login to Trigger.dev` etc.
1413
*
1514
* Remix v2 picks the meta of the deepest route that exports one; a route without a meta export
1615
* inherits its nearest ancestor's. So a layout's `pageMeta` is the fallback for children that
@@ -20,9 +19,6 @@ import { appEnvTitleTag } from "~/utils";
2019

2120
const APP_NAME = "Trigger.dev";
2221

23-
/** The org route holds the project/environment the URL resolved to. */
24-
const ORGANIZATION_MATCH_ID = "routes/_app.orgs.$organizationSlug";
25-
2622
/** One or more title segments, most specific first: `["run_abc", "Runs"]`. */
2723
export type TitleSegments = string | string[];
2824

@@ -56,53 +52,13 @@ export function pageMeta<TLoader = unknown>(page: PageInput<TLoader>): MetaFunct
5652
};
5753
}
5854

59-
/** Builds the full title from the page segments plus the scope found in `matches`. */
55+
/** Builds the full title from the page segments plus the app title. */
6056
export function composePageTitle(segments: string[], matches: Matches): string {
61-
return [...segments, scopeFromMatches(matches), appTitle(appEnvFromMatches(matches))]
57+
return [...segments, appTitle(appEnvFromMatches(matches))]
6258
.filter((segment): segment is string => Boolean(segment))
6359
.join(" | ");
6460
}
6561

66-
export function scopeFromMatches(matches: Matches): string | undefined {
67-
const match = matches.find((m) => m.id === ORGANIZATION_MATCH_ID);
68-
const data = match?.data as
69-
| {
70-
organization?: { title?: string | null };
71-
project?: { name?: string | null };
72-
environment?: { type?: string | null; branchName?: string | null } | null;
73-
}
74-
| undefined;
75-
76-
if (!data) return undefined;
77-
78-
// The org loader resolves a "best" project even on org-scoped URLs that name none, so
79-
// the project scope is only honest when the URL actually carries one.
80-
const project = match?.params?.projectParam ? data.project?.name : undefined;
81-
if (project) {
82-
const environment = data.environment ? environmentLabel(data.environment) : undefined;
83-
return environment ? `${project} (${environment})` : project;
84-
}
85-
86-
return data.organization?.title ?? undefined;
87-
}
88-
89-
function environmentLabel(environment: { type?: string | null; branchName?: string | null }) {
90-
if (environment.branchName) return environment.branchName;
91-
92-
switch (environment.type) {
93-
case "PRODUCTION":
94-
return "Prod";
95-
case "STAGING":
96-
return "Staging";
97-
case "DEVELOPMENT":
98-
return "Dev";
99-
case "PREVIEW":
100-
return "Preview";
101-
default:
102-
return undefined;
103-
}
104-
}
105-
10662
function appEnvFromMatches(matches: Matches): string | undefined {
10763
const rootData = matches[0]?.data as { appEnv?: string } | undefined;
10864
return rootData?.appEnv;

0 commit comments

Comments
 (0)