mirror of
https://github.com/makeplane/plane.git
synced 2026-08-29 10:08:51 +02:00
release: v1.4.2 #9632
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "admin",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "Admin UI for Plane",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plane-api",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "API server powering Plane's backend",
|
||||
"license": "AGPL-3.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "live",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "A realtime collaborative server powers Plane's rich text editor",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "space",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -9,9 +9,27 @@ import { hydrateRoot } from "react-dom/client";
|
||||
import { HydratedRouter } from "react-router/dom";
|
||||
|
||||
import polyfills from "@/lib/polyfills";
|
||||
import { isStaleAssetErrorMessage, recoverFromStaleAsset } from "@/lib/stale-asset-error";
|
||||
|
||||
void polyfills;
|
||||
|
||||
// Production-only: in dev these errors come from the dev server itself (restarts,
|
||||
// stale optimized deps) and auto-reloading would mask them.
|
||||
if (import.meta.env.PROD) {
|
||||
window.addEventListener("vite:preloadError", (event) => {
|
||||
if (recoverFromStaleAsset()) event.preventDefault();
|
||||
});
|
||||
|
||||
window.addEventListener("error", (event) => {
|
||||
if (isStaleAssetErrorMessage(event.message || "")) recoverFromStaleAsset();
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (event) => {
|
||||
const reason = event.reason instanceof Error ? event.reason.message : String(event.reason ?? "");
|
||||
if (isStaleAssetErrorMessage(reason)) recoverFromStaleAsset();
|
||||
});
|
||||
}
|
||||
|
||||
startTransition(() => {
|
||||
hydrateRoot(
|
||||
document,
|
||||
|
||||
@@ -24,6 +24,8 @@ import globalStyles from "@/styles/globals.css?url";
|
||||
import type { Route } from "./+types/root";
|
||||
// components
|
||||
import { LogoSpinner } from "@/components/common/logo-spinner";
|
||||
// lib
|
||||
import { isStaleAssetError, recoverFromStaleAsset } from "@/lib/stale-asset-error";
|
||||
// local
|
||||
import { CustomErrorComponent } from "./error";
|
||||
import { AppProvider } from "./provider";
|
||||
@@ -146,5 +148,10 @@ export function HydrateFallback() {
|
||||
}
|
||||
|
||||
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
||||
// A stale chunk failure surfaces here as React Router's own wrapper error
|
||||
// (the failed dynamic import itself never reaches a window event) — recover
|
||||
// the same way entry.client.tsx does instead of just showing the error page.
|
||||
if (import.meta.env.PROD && isStaleAssetError(error)) recoverFromStaleAsset();
|
||||
|
||||
return <CustomErrorComponent error={error} />;
|
||||
}
|
||||
|
||||
66
apps/web/core/lib/stale-asset-error.ts
Normal file
66
apps/web/core/lib/stale-asset-error.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
// Error signatures produced when a deploy removes hashed assets that an open tab
|
||||
// still references (stale-tab version skew).
|
||||
const STALE_ASSET_ERROR_SIGNATURES = [
|
||||
"Failed to fetch dynamically imported module", // Chromium
|
||||
"error loading dynamically imported module", // Firefox
|
||||
"Importing a module script failed", // Safari
|
||||
"Unable to preload CSS", // Vite preload helper
|
||||
// React Router's own wrapper when a route module's lazy import fails during
|
||||
// navigation — the original browser error above never reaches here as a
|
||||
// window event, only this synthesized message.
|
||||
"No result returned from dataStrategy for route",
|
||||
];
|
||||
|
||||
export const isStaleAssetErrorMessage = (message: string): boolean =>
|
||||
STALE_ASSET_ERROR_SIGNATURES.some((signature) => message.includes(signature));
|
||||
|
||||
export const isStaleAssetError = (error: unknown): boolean => {
|
||||
const message = error instanceof Error ? error.message : typeof error === "string" ? error : "";
|
||||
return isStaleAssetErrorMessage(message);
|
||||
};
|
||||
|
||||
// Reload-once-then-fall-through-to-boundary guard, shared by the window-level
|
||||
// listeners (entry.client.tsx) and the route ErrorBoundary (root.tsx) so both
|
||||
// paths to the same failure share one reload attempt instead of racing.
|
||||
const STALE_ASSET_RELOAD_KEY = "__plane_chunk_reload";
|
||||
const STALE_ASSET_RELOAD_WINDOW_MS = 30_000;
|
||||
|
||||
const hasRecentStaleAssetReload = (): boolean => {
|
||||
try {
|
||||
const lastReloadAt = Number(sessionStorage.getItem(STALE_ASSET_RELOAD_KEY));
|
||||
return Number.isFinite(lastReloadAt) && Date.now() - lastReloadAt < STALE_ASSET_RELOAD_WINDOW_MS;
|
||||
} catch {
|
||||
// No storage means no loop guard — never auto-reload in that case.
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
let isRecoveringFromStaleAsset = false;
|
||||
|
||||
// Returns whether it actually triggered a reload, so callers that can otherwise
|
||||
// fall back to default browser error handling (e.g. Vite's preload-error
|
||||
// overlay) only suppress that fallback when a reload is really in flight.
|
||||
export const recoverFromStaleAsset = (): boolean => {
|
||||
if (isRecoveringFromStaleAsset) return false;
|
||||
isRecoveringFromStaleAsset = true;
|
||||
|
||||
if (hasRecentStaleAssetReload()) {
|
||||
// Second failure in a row — surface to the route error boundary instead of looping.
|
||||
isRecoveringFromStaleAsset = false;
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(STALE_ASSET_RELOAD_KEY, String(Date.now()));
|
||||
} catch {
|
||||
isRecoveringFromStaleAsset = false;
|
||||
return false;
|
||||
}
|
||||
window.location.reload();
|
||||
return true;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "plane",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "Open-source project management that unlocks customer value",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/codemods",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/constants",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/editor",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "Core Editor that powers Plane",
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/hooks",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "React hooks that are shared across multiple apps internally",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/i18n",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "I18n shared across multiple apps internally",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/logger",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "Logger shared across multiple apps internally",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/propel",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/services",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/shared-state",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "Shared state shared across multiple apps internally",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/tailwind-config",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "common tailwind configuration across monorepo",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/types",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/typescript-config",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/ui",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "UI components shared across multiple apps internally",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/utils",
|
||||
"version": "1.4.1",
|
||||
"version": "1.4.2",
|
||||
"private": true,
|
||||
"description": "Helper functions shared across multiple apps internally",
|
||||
"license": "AGPL-3.0",
|
||||
|
||||
Reference in New Issue
Block a user