Skip to content

Commit 7747073

Browse files
authored
Merge branch 'main' into release-03-11-2025.staging
2 parents 2688af3 + 1f2e7a2 commit 7747073

47 files changed

Lines changed: 232 additions & 10 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/builder/app/builder/features/pages/page-utils.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ registerContainers();
3737
const initialSystem = {
3838
origin: "https://undefined.wstd.work",
3939
params: {},
40+
pathname: "/",
4041
search: {},
4142
};
4243

@@ -586,6 +587,7 @@ test("page root scope should provide page system variable value", () => {
586587
$ws$dataSource$systemId: {
587588
origin: "https://undefined.wstd.work",
588589
params: {},
590+
pathname: "/",
589591
search: {},
590592
},
591593
},
@@ -594,6 +596,7 @@ test("page root scope should provide page system variable value", () => {
594596
"systemId",
595597
{
596598
params: {},
599+
pathname: "/",
597600
search: {},
598601
origin: "https://undefined.wstd.work",
599602
},
@@ -608,6 +611,7 @@ test("page root scope should provide page system variable value", () => {
608611
scope: {
609612
$ws$dataSource$systemId: {
610613
params: { slug: "my-post" },
614+
pathname: "/",
611615
search: {},
612616
origin: "https://undefined.wstd.work",
613617
},
@@ -617,6 +621,7 @@ test("page root scope should provide page system variable value", () => {
617621
"systemId",
618622
{
619623
params: { slug: "my-post" },
624+
pathname: "/",
620625
search: {},
621626
origin: "https://undefined.wstd.work",
622627
},

apps/builder/app/builder/features/settings-panel/resource-panel.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import {
2525
SYSTEM_VARIABLE_ID,
2626
systemParameter,
2727
} from "@webstudio-is/sdk";
28-
import { serializeValue, sitemapResourceUrl } from "@webstudio-is/sdk/runtime";
28+
import {
29+
serializeValue,
30+
sitemapResourceUrl,
31+
currentDateResourceUrl,
32+
} from "@webstudio-is/sdk/runtime";
2933
import {
3034
Box,
3135
Flex,
@@ -989,6 +993,12 @@ export const SystemResourceForm = forwardRef<
989993
value: JSON.stringify(sitemapResourceUrl),
990994
description: "Resource that loads the sitemap data of the current site.",
991995
},
996+
{
997+
label: "Current Date",
998+
value: JSON.stringify(currentDateResourceUrl),
999+
description:
1000+
"Provides current date information (year, month, day) normalized to midnight UTC. Time components are set to 00:00:00 to prevent React hydration errors.",
1001+
},
9921002
];
9931003

9941004
const [localResource, setLocalResource] = useState(() => {

apps/builder/app/routes/rest.resources-loader.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { type ActionFunctionArgs, data } from "@remix-run/server-runtime";
33
import { ResourceRequest } from "@webstudio-is/sdk";
44
import { isLocalResource, loadResource } from "@webstudio-is/sdk/runtime";
55
import { loader as siteMapLoader } from "../shared/$resources/sitemap.xml.server";
6+
import { loader as currentDateLoader } from "../shared/$resources/current-date.server";
67
import { preventCrossOriginCookie } from "~/services/no-cross-origin-cookie";
78
import { checkCsrf } from "~/services/csrf-session.server";
89
import { getResourceKey } from "~/shared/resources";
@@ -21,6 +22,10 @@ export const action = async ({ request }: ActionFunctionArgs) => {
2122
return siteMapLoader({ request });
2223
}
2324

25+
if (isLocalResource(input, "current-date")) {
26+
return currentDateLoader({ request });
27+
}
28+
2429
return fetch(input, init);
2530
};
2631

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { json } from "@remix-run/server-runtime";
2+
import { parseBuilderUrl } from "@webstudio-is/http-client";
3+
import { isBuilder } from "../router-utils";
4+
5+
/**
6+
* System Resource that provides current date information.
7+
* This prevents React hydration errors when displaying dynamic dates
8+
* (e.g., copyright years in footers) by ensuring server and client
9+
* render the same date.
10+
*
11+
* All values are normalized to midnight UTC (00:00:00.000Z) to ensure
12+
* consistency throughout the entire day, preventing hydration mismatches.
13+
*/
14+
export const loader = async ({ request }: { request: Request }) => {
15+
if (isBuilder(request) === false) {
16+
throw new Error("Only builder requests are allowed");
17+
}
18+
19+
const { projectId } = parseBuilderUrl(request.url);
20+
21+
if (projectId === undefined) {
22+
throw new Error("projectId is required");
23+
}
24+
25+
const now = new Date();
26+
27+
// Normalize to midnight UTC to prevent hydration mismatches
28+
const startOfDay = new Date(
29+
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
30+
);
31+
32+
return json({
33+
iso: startOfDay.toISOString(),
34+
year: startOfDay.getUTCFullYear(),
35+
month: startOfDay.getUTCMonth() + 1, // 1-12 instead of 0-11
36+
day: startOfDay.getUTCDate(),
37+
timestamp: startOfDay.getTime(),
38+
});
39+
};

apps/builder/app/shared/nano-states/props.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { $resourcesCache, getResourceKey } from "../resources";
3636
const initialSystem = {
3737
origin: "https://undefined.wstd.work",
3838
params: {},
39+
pathname: "/",
3940
search: {},
4041
};
4142

@@ -917,6 +918,7 @@ test("provide page system variable value", () => {
917918
?.get(systemId)
918919
).toEqual({
919920
params: { slug: "my-post" },
921+
pathname: "/",
920922
search: {},
921923
origin: "https://undefined.wstd.work",
922924
});
@@ -948,6 +950,7 @@ test("provide global system variable value", () => {
948950
});
949951
const updatedSystem = {
950952
params: { slug: "my-post" },
953+
pathname: "/",
951954
search: {},
952955
origin: "https://undefined.wstd.work",
953956
};

apps/builder/app/shared/system.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,20 @@ export const $currentSystem = computed(
3434
const system: System = {
3535
search: {},
3636
params: {},
37+
pathname: "/",
3738
origin,
3839
};
3940
if (page === undefined) {
4041
return system;
4142
}
4243
const systemData = systemByPage.get(page.id);
4344
const extractedParams = extractParams(page.path, page.history?.[0]);
45+
const params = { ...extractedParams, ...systemData?.params };
46+
const pathname = compilePath(page.path, params) || "/";
4447
return {
4548
search: { ...system.search, ...systemData?.search },
46-
params: { ...extractedParams, ...systemData?.params },
49+
params,
50+
pathname,
4751
origin,
4852
};
4953
}

fixtures/react-router-cloudflare/app/routes/[another-page]._index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const loader = async (arg: LoaderFunctionArgs) => {
7070
params,
7171
search: Object.fromEntries(url.searchParams),
7272
origin: url.origin,
73+
pathname: url.pathname,
7374
};
7475

7576
const resources = await loadResources(
@@ -203,6 +204,7 @@ export const action = async ({
203204
params: {},
204205
search: {},
205206
origin: url.origin,
207+
pathname: url.pathname,
206208
};
207209

208210
const resourceName = formData.get(formIdFieldName);

fixtures/react-router-cloudflare/app/routes/_index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const loader = async (arg: LoaderFunctionArgs) => {
7070
params,
7171
search: Object.fromEntries(url.searchParams),
7272
origin: url.origin,
73+
pathname: url.pathname,
7374
};
7475

7576
const resources = await loadResources(
@@ -203,6 +204,7 @@ export const action = async ({
203204
params: {},
204205
search: {},
205206
origin: url.origin,
207+
pathname: url.pathname,
206208
};
207209

208210
const resourceName = formData.get(formIdFieldName);

fixtures/react-router-docker/app/routes/[another-page]._index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const loader = async (arg: LoaderFunctionArgs) => {
7070
params,
7171
search: Object.fromEntries(url.searchParams),
7272
origin: url.origin,
73+
pathname: url.pathname,
7374
};
7475

7576
const resources = await loadResources(
@@ -203,6 +204,7 @@ export const action = async ({
203204
params: {},
204205
search: {},
205206
origin: url.origin,
207+
pathname: url.pathname,
206208
};
207209

208210
const resourceName = formData.get(formIdFieldName);

fixtures/react-router-docker/app/routes/_index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const loader = async (arg: LoaderFunctionArgs) => {
7070
params,
7171
search: Object.fromEntries(url.searchParams),
7272
origin: url.origin,
73+
pathname: url.pathname,
7374
};
7475

7576
const resources = await loadResources(
@@ -203,6 +204,7 @@ export const action = async ({
203204
params: {},
204205
search: {},
205206
origin: url.origin,
207+
pathname: url.pathname,
206208
};
207209

208210
const resourceName = formData.get(formIdFieldName);

0 commit comments

Comments
 (0)