pnpm's default strict isolation hides @babel/runtime/helpers/* and other
transitive deps from Metro, which expects flat node_modules layout.
Each missing dep was a separate failed EAS build (metro-runtime,
@babel/runtime, ...) — added .npmrc with public-hoist-pattern entries
that catch the common RN/Expo families so we stop whack-a-moling them.
Also kept @babel/runtime as direct dep of @chat-app/mobile for safety.
expo-router@4 imports @expo/metro-runtime as the very first thing in its
entry-classic.js so Fast Refresh works; bundler errored without it.
Also dropped expo-router/babel from babel.config.js — its functionality
moved into babel-preset-expo in SDK 50.
Phase 0 installed expo-application/expo-image-picker/expo-dev-client at
^55.x assuming the major version matched the SDK number. It doesn't —
each expo-* package has its own major. Misaligned versions pulled in
expo-modules-core@2.2.3 which expects an API that SDK 52's gradle plugin
host doesn't provide, breaking the Gradle build with
'expo-module-gradle-plugin not found'.
`expo install --fix` aligned everything to SDK 52 baselines:
expo-application 55.0.15 -> ~6.0.2
expo-dev-client 55.0.33 -> ~5.0.20
expo-image-picker 55.0.20 -> ~16.0.6
expo-sqlite 15.0.6 -> ~15.1.4
react-native 0.76.0 -> 0.76.9
+ RN companion libs (gesture-handler, screens, async-storage)
The 2500ms timer that drifts CallState from `ended` back to `idle` was
an inline magic number. Promote to a module-level constant with a
comment explaining why the value isn't arbitrary — picked from the
post-Phase-3 quality review.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Phase-3 implementer ASCII'd four strings to side-step a Windows
console encoding issue during the apply step. The TypeScript / Metro
toolchain handles non-ASCII string literals cleanly — the workaround
was unnecessary and produced ugly UI labels ("Anruf lauft", "Hoerer",
"Verbinde ..."). Restore the originals.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three small follow-ups from the post-Phase-1 quality review:
* authContext.tsx — drop the dead `userId` extraction + the `void
userId` suppressor that masked an unused-locals warning. The session
is already implicitly threaded through the supabase client, so no
consumer of ensureDevice needed the value.
* authContext.tsx — switch the device-name string concat to a
template literal for consistency with the rest of the codebase.
* ErrorBoundary.tsx — replace the four inline hex literals with their
`theme/colors.ts` constants. The boundary was authored in Phase 0
before the theme module existed; this brings it in line with every
Phase 1 screen.
* apps/mobile/README.md — drop the stale Phase-0 paragraph about the
`lib/sharedSmoke.ts` canary (deleted in Phase 1) and add a short
pointer to the env-var setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`appVersion: process.env.npm_package_version ?? '0.0.0'` only worked in
dev — pnpm sets that env var while running its lifecycle scripts. In
the packaged Electron build npm_package_version is unset, so every
installed user saw `v0.0.0` on the Changelog page and the version badge
permanently flagged them as "Update verfügbar" against their own
actually-current version.
Replace with a static `import pkg from '../package.json'` so Vite
inlines the version string into the preload bundle at build time. The
release script bumps package.json before electron-builder runs, so the
inlined value always matches the freshly-released version.
resolveJsonModule + esModuleInterop are already on in tsconfig.node.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both right-hand panels used absolute inset-y-0 right-0 and floated on
top of the conversation, hiding the messages directly underneath the
panel and looking unlike Discord's actual layout. Restructure:
* MediaFilesDrawer: drop absolute/z-index/shadow chrome, become a
static flex column (w-[380px] shrink-0) with a left border. Internal
layout unchanged.
* GroupInfoPanel: same treatment (w-[320px] shrink-0). Dropped the
backdrop-blur and slide-up animation that only made sense as a modal.
* ConversationPage: wrap the chat content (voice rail, in-call panel,
messages list, drag-overlay, input form) in a new
`flex min-w-0 flex-1 flex-col` chat-column, and make that column a
sibling of the drawers inside a new `flex flex-1 flex-row` row. The
conversation header + search bar stay full-width above the row.
Result: opening a drawer narrows the chat column instead of covering
it, matching Discord's behaviour. The chat-column wrapper also carries
the `relative` anchor previously held by the outer wrapper so the
drag-and-drop overlay positions correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
In group conversations the "Outgoing/Incoming/Missed call" system pill
gave no clue WHO triggered the event — fine in a 1:1 where the only two
players are obvious, useless in a group with three+ members. Discord
puts the caller's name in the pill; mirror that.
CallEventRow now takes a senderDisplayName prop (plumbed through from
MessageBubble) and switches non-own labels to the name-aware variants:
* ended + !mine + name → "{name} hat einen Anruf gestartet"
* missed + !mine + name → "Verpasster Anruf von {name}"
* declined + !mine + name → "Anruf von {name} abgelehnt"
Own events (mine) stay generic ("Outgoing call" / "No answer") since
the user already knows they were the initiator. Fallback path without
a name keeps the previous generic labels so nothing regresses if the
sender is unresolvable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>