Without postcss.config.mjs, @tailwindcss/postcss never ran during
'next build', producing a CSS bundle with only @font-face rules (33KB)
and zero Tailwind utility classes. The UI rendered as unstyled HTML
(black background, white text, no spacing).
- Add web/postcss.config.mjs wiring @tailwindcss/postcss (matches the
pattern used by sibling repos like ChronoMind)
- Copy postcss.config.mjs into the web Docker build stage so 'pnpm run
build' can resolve it
Root cause: docker-compose.yml hardcoded NEXT_PUBLIC_NOTES_API_URL to
https://api.bytelyst.com/notelett — a production URL that doesn't
exist on this network — as the *build arg* for the web image. The
docker-compose.override.yml correctly set localhost:4016/api but only
on the runtime environment, which has no effect because NEXT_PUBLIC_*
values are baked into the Next.js bundle at build time (pnpm run build
inside the Dockerfile), not read at runtime.
Symptom: every authenticated client-side fetch from the deployed web
container went to https://api.bytelyst.com/notelett/... which the
corporate proxy intercepted with a blockpage. The saved-views client
in particular fired on every (app)/ layout mount, surfacing a
'Failed to fetch' toast on dashboard load. 4 release-flows.spec
tests failed because page.route('**/api/**') couldn't match the
api.bytelyst.com URLs at all.
Discovery: inspected the deployed bundle inside the running container.
'grep -oE "api.bytelyst.com" /app/web/web/.next/static/chunks/*.js'
returned multiple hits across the (app)/ layout, (auth)/ pages, and
share page. The string was absent from the source tree, which proved
it had been injected at build time via the broken arg default.
Discovery debug pattern (kept for future use):
page.on('requestfailed', r => console.log(r.method(), r.url()));
page.route('**/api/**', route => route.fulfill({status:200,body:'{}'}));
await page.goto('/dashboard');
// FAILED REQUESTS will list any URL not under /api/** that the SPA
// attempted, exposing baked-in production URLs immediately.
Fix (three layers, defense in depth):
1. docker-compose.yml — replace hardcoded
'NEXT_PUBLIC_NOTES_API_URL: https://api.bytelyst.com/notelett'
in the build.args block with
'${NEXT_PUBLIC_NOTES_API_URL:-http://localhost:4016/api}'.
Same treatment for the runtime environment block. Add build args
for the four other NEXT_PUBLIC_* values (extraction, MCP,
diagnostics, product name/id, telemetry transport) so a single env
var on the host controls both build and runtime layers.
2. web/Dockerfile — declare ARG and ENV lines for all seven
NEXT_PUBLIC_* values so the build args reach 'pnpm run build'.
Previously only NOTES_API_URL and PLATFORM_SERVICE_URL were
declared, which meant overriding extraction/MCP/diagnostics via
docker compose silently had no effect on the bundle.
3. docker-compose.override.yml — add a build.args block mirroring the
four URL overrides so the local-only override also reaches build
time, not just runtime. Comment block explains the bake-time vs
runtime distinction so future contributors don't repeat the bug.
Verified end-to-end after the fix:
- docker compose build --no-cache web + up -d → grep of bundle now
shows 'localhost:4016/api', api.bytelyst.com fully gone.
- Debug interception test: zero requestfailed events on /dashboard.
- Playwright release-flows.spec.ts: 4 failed → 4 passed (after URL
fix; no test code changed for these four tests).
- Full Playwright suite (--ignore-snapshots): 43 passed.
- scripts/e2e-docker-test.sh: 9/9 backend API lifecycle steps pass.
- pnpm run verify: backend 380/380, web 96/96, mobile 97/97.
Root cause of bug: web Dockerfile copied .next/static to the wrong path
in the runtime stage. The Next.js 16 standalone server (CMD 'node
web/server.js' from /app/web) runs from /app/web/web/server.js because
'standalone' wraps the source directory. It serves /_next/static/* from
'./web/.next/static' (relative to the standalone server's location),
not from './.next/static' (which is what the previous COPY produced).
Symptom: in the deployed Docker stack at http://localhost:3050 every
client-side JS chunk under /_next/static/chunks/* returned HTTP 404
with content-type text/plain. The browser refused to execute the
chunks (strict MIME), so the SPA never hydrated. All Playwright tests
that ask for any dynamic UI text on a (app)/ page would time out
because AuthGuard never ran in the browser.
Discovery path: deployed compose stack via 'docker compose up -d
--build' + 'scripts/e2e-docker-test.sh' (backend API 9/9 ✓), then ran
Playwright against NOTELETT_WEB_PORT=3050. settings.spec failed with
'product configuration section' not visible. Page snapshot showed
just <skip-to-content link> + toast region — no other content. Console
logs revealed every /_next/static/chunks/* was 404 with text/plain.
'docker exec ls' showed BUILD_ID at /app/web/web/.next/BUILD_ID and
static at /app/web/.next/static — wrong path. Moved static into the
standalone tree and chunks now serve 200 with application/javascript.
Fix:
web/Dockerfile: change
COPY --from=builder /app/web/.next/static ./.next/static
to
COPY --from=builder /app/web/.next/static ./web/.next/static
with explanatory comment so this doesn't regress.
Test hardening (these tests were dev-server-only by accident — they
worked locally because Next.js dev did not enforce the same static
path layout; the bug above hid them in production builds too):
web/e2e/accessibility.spec.ts — 'focus-visible ring appears on tab
navigation' was navigating to /dashboard which AuthGuard correctly
redirects when unauthenticated, leaving the DOM empty (AuthGuard
returns null until verifySessionAndReadiness completes) so Tab
presses focused nothing. Switched to /login which is unauthenticated
by design and has known focusable form inputs.
web/e2e/settings.spec.ts — 'shows product configuration section'
expected /settings to render content without auth. Now obtains real
tokens from platform-service via API, seeds them via addInitScript,
and falls back to test.skip with a clear message if platform-service
is not reachable.
Verified:
- All 31 Playwright tests across navigation/accessibility/dashboard/
search/settings/smart-actions/reviews specs PASS against the
deployed Docker stack at :3050.
- 'pnpm run verify': backend 380/380, web 96/96, mobile 97/97.
- 'bash scripts/e2e-docker-test.sh': 9/9 backend API CRUD steps pass.
- 'curl -sI http://localhost:3050/_next/static/chunks/app/error-*.js'
now returns 200 + application/javascript.
Not migrated: e2e/release-flows.spec.ts and e2e/visual-regression.spec.ts
intentionally remain dev-server-targeted. release-flows.spec uses
page.route() to mock backend responses and is meant to test the UI in
isolation against a dev server. visual-regression.spec needs baseline
regeneration after the UI5-UI8 migration; this is a separate workstream
tracked in docs/UI_UX_PLATFORM_CORE_ROADMAP.md.
- Fixed NEXT_PUBLIC_NOTES_API_URL to use public API endpoint
- Updated docker-compose.yml environment format to proper YAML
- Updated Dockerfiles to remove Gitea secrets and use .docker-deps
- Added docker-prep.sh script for dependency packaging
- Changed NODE_ENV back to development for compatibility with memory DB
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The base image approach is too complex for the current pnpm workspace structure.
Products cannot easily use the base image's workspace because pnpm expects all
workspace packages to be present during install. Reverting to the proven
docker-prep.sh tarball approach for now.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The base image only includes production dependencies, so we need to install
all dependencies (including devDependencies) in the builder stage to have
TypeScript and Next.js available for building.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Update Dockerfiles to use bytelyst-common-base-backend and bytelyst-common-base-web
images instead of installing @bytelyst/* packages via tarballs.
Benefits:
- Smaller final images (~50MB vs ~250MB)
- Faster builds (base image cached)
- Consistent package versions across products
- No need for docker-prep.sh tarball packing
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Alpine breaks under corporate proxy TLS interception. Debian slim
works reliably. NODE_TLS_REJECT_UNAUTHORIZED=0 removed from production
stages — only kept in build stages where npm registries need it.