diff --git a/.claude/coding-standards.md b/.claude/coding-standards.md
index d6bcd6d0..07ac4d02 100644
--- a/.claude/coding-standards.md
+++ b/.claude/coding-standards.md
@@ -2,6 +2,16 @@
This document outlines coding conventions and standards for this project.
+## Branch Workflow (MUST DO FIRST)
+
+**Before making any code changes, create a feature branch off main.** Never work directly on main.
+
+```bash
+git checkout -b feat/descriptive-branch-name
+```
+
+This is the very first step for every task — no exceptions. If you find yourself about to push to main, **STOP IMMEDIATELY** and ask the user what to do instead.
+
## Strict Typing and Nullability
Prefer strict, explicit typings and clear nullability rules; don't auto-widen.
diff --git a/.github/workflows/build_and_push_nonproduction_images.yml b/.github/workflows/build_and_push_nonproduction_images.yml
index b3952a7c..ab63e862 100644
--- a/.github/workflows/build_and_push_nonproduction_images.yml
+++ b/.github/workflows/build_and_push_nonproduction_images.yml
@@ -162,6 +162,7 @@ jobs:
NEXT_PUBLIC_BACKEND_SERVICE_API_PATH=${{ vars.BACKEND_SERVICE_API_PATH }}
NEXT_PUBLIC_BACKEND_API_VERSION=${{ vars.BACKEND_API_VERSION }}
NEXT_PUBLIC_TIPTAP_APP_ID=${{ vars.TIPTAP_APP_ID }}
+ GIT_COMMIT_SHA=${{ github.sha }}
FRONTEND_SERVICE_PORT=${{ vars.FRONTEND_SERVICE_PORT }}
FRONTEND_SERVICE_INTERFACE=${{ vars.FRONTEND_SERVICE_INTERFACE }}
tags: ${{ steps.tags.outputs.frontend_tags }}
diff --git a/Dockerfile b/Dockerfile
index 5542e06c..91d73fd3 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,6 +13,7 @@ ARG NEXT_PUBLIC_BACKEND_SERVICE_API_PATH
ARG NEXT_PUBLIC_BACKEND_API_VERSION
ARG NEXT_PUBLIC_TIPTAP_APP_ID
ARG NEXT_PUBLIC_BASE_PATH
+ARG GIT_COMMIT_SHA
ARG FRONTEND_SERVICE_INTERFACE
ARG FRONTEND_SERVICE_PORT
@@ -45,6 +46,7 @@ ARG NEXT_PUBLIC_BACKEND_SERVICE_API_PATH
ARG NEXT_PUBLIC_BACKEND_API_VERSION
ARG NEXT_PUBLIC_TIPTAP_APP_ID
ARG NEXT_PUBLIC_BASE_PATH
+ARG GIT_COMMIT_SHA
# Pass them as ENV so Next.js static build can access
ENV NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL=$NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL
@@ -54,6 +56,7 @@ ENV NEXT_PUBLIC_BACKEND_SERVICE_API_PATH=$NEXT_PUBLIC_BACKEND_SERVICE_API_PATH
ENV NEXT_PUBLIC_BACKEND_API_VERSION=$NEXT_PUBLIC_BACKEND_API_VERSION
ENV NEXT_PUBLIC_TIPTAP_APP_ID=$NEXT_PUBLIC_TIPTAP_APP_ID
ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH
+ENV GIT_COMMIT_SHA=$GIT_COMMIT_SHA
# Build the Next.js application
# Note: Use `next build` to build the application for production
@@ -86,6 +89,7 @@ ARG NEXT_PUBLIC_BACKEND_SERVICE_API_PATH
ARG NEXT_PUBLIC_BACKEND_API_VERSION
ARG NEXT_PUBLIC_TIPTAP_APP_ID
ARG NEXT_PUBLIC_BASE_PATH
+ARG GIT_COMMIT_SHA
ARG FRONTEND_SERVICE_INTERFACE
ARG FRONTEND_SERVICE_PORT
@@ -96,6 +100,7 @@ ENV NEXT_PUBLIC_BACKEND_SERVICE_API_PATH=$NEXT_PUBLIC_BACKEND_SERVICE_API_PATH
ENV NEXT_PUBLIC_BACKEND_API_VERSION=$NEXT_PUBLIC_BACKEND_API_VERSION
ENV NEXT_PUBLIC_TIPTAP_APP_ID=$NEXT_PUBLIC_TIPTAP_APP_ID
ENV NEXT_PUBLIC_BASE_PATH=$NEXT_PUBLIC_BASE_PATH
+ENV GIT_COMMIT_SHA=$GIT_COMMIT_SHA
# Runtime ENV for Compose
ENV HOSTNAME=$FRONTEND_SERVICE_INTERFACE
diff --git a/__tests__/app/global-error.test.tsx b/__tests__/app/global-error.test.tsx
new file mode 100644
index 00000000..61a2d175
--- /dev/null
+++ b/__tests__/app/global-error.test.tsx
@@ -0,0 +1,171 @@
+import { render, screen, waitFor } from '@testing-library/react'
+import userEvent from '@testing-library/user-event'
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
+import GlobalError from '@/app/global-error'
+
+// ---------------------------------------------------------------------------
+// Integration tests for global-error.tsx
+//
+// Tests the auto-reload behavior for ChunkLoadErrors and the
+// sessionStorage-based infinite loop prevention.
+// ---------------------------------------------------------------------------
+
+// Mock window.location.reload — jsdom doesn't support real navigation
+const reloadMock = vi.fn()
+
+beforeEach(() => {
+ sessionStorage.clear()
+ reloadMock.mockClear()
+
+ Object.defineProperty(window, 'location', {
+ value: { ...window.location, reload: reloadMock },
+ writable: true,
+ configurable: true,
+ })
+})
+
+afterEach(() => {
+ sessionStorage.clear()
+})
+
+function chunkLoadError(message = 'Loading chunk abc123 failed'): Error & { digest?: string } {
+ const error = new Error(message)
+ error.name = 'ChunkLoadError'
+ return error
+}
+
+function dynamicImportError(): Error & { digest?: string } {
+ return new Error('Failed to fetch dynamically imported module: /path/to/chunk.js')
+}
+
+function genericError(): Error & { digest?: string } {
+ return new Error('Cannot read properties of undefined')
+}
+
+describe('GlobalError', () => {
+ describe('ChunkLoadError auto-reload', () => {
+ it('reloads the page on first ChunkLoadError', async () => {
+ const reset = vi.fn()
+
+ render(
+ {isChunkError + ? "A new version is available. Reloading..." + : "An unexpected error occurred."} +
+ {!isChunkError && ( + + )} +