Skip to content

Add expo mobile app - #10

Open
PaulgSmith wants to merge 2 commits into
rubyforgood:mainfrom
PaulgSmith:add-expo-mobile-app
Open

Add expo mobile app#10
PaulgSmith wants to merge 2 commits into
rubyforgood:mainfrom
PaulgSmith:add-expo-mobile-app

Conversation

@PaulgSmith

Copy link
Copy Markdown
Collaborator

No description provided.

PaulgSmith and others added 2 commits August 2, 2026 16:30
Adds a React Native app in mobile/ (Expo SDK 57, React 19, RN 0.86,
expo-router, TypeScript) and the Rails API it talks to.

Rails side:
  - Api::BaseController inherits ActionController::API, so the phone is
    not subject to CSRF, cookies or the allow_browser version check, and
    renders 404/400/422 as predictable JSON.
  - Api::V1::TasksController exposes CRUD at /api/v1/tasks. Task is a
    placeholder resource wired end to end, meant to be replaced.
  - rack-cors for the Expo web target. Native builds ignore CORS.
  - Development Host Authorization now accepts .local hostnames, which
    were 403ing requests from phones resolving the host over mDNS.

Mobile side:
  - src/lib/api.ts derives the API host from the Metro bundler address
    instead of hardcoding localhost, which resolves differently on the
    iOS simulator, the Android emulator (10.0.2.2) and real devices.
    EXPO_PUBLIC_API_URL overrides it and is required for release builds.
  - Tasks screen lists, creates, toggles and deletes through the API,
    updating optimistically and rolling back on failure.

Housekeeping: rubocop, brakeman and the Docker build context skip
mobile/, whose node_modules ship Ruby CocoaPods scripts that would
otherwise be linted and scanned as our own. WSL Zone.Identifier files
are now gitignored.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The SDK 57 rescaffold reset several things that had been customised on
the earlier add-expo-mobile branch. Carried them across:

  - app.json: name "Along With You", slug along-with-you, and the
    alongwithyou:// URL scheme. The scaffold defaults meant the app
    installed on a phone as "mobile" and claimed the mobile:// scheme,
    which matters as soon as OAuth callbacks or push notifications
    need a deep link.
  - package.json: name alongwithyou-mobile, plus the typecheck script.
  - engines.node, set to >=20.19.4 rather than the previous >=20.19.0,
    because React Native 0.86 will not build below that.
  - eslint, installed at the versions matching SDK 57 rather than the
    SDK 54 ones, and mobile/README.md rewritten for the src/ layout and
    the API client, which the old copy predates.

Two react-hooks/set-state-in-effect errors surfaced once linting ran.
Loading tasks no longer sets state before its first await, which was a
real cascading render. The remaining report on the mount effect, and one
in Expo's own use-color-scheme.web.ts, are suppressed inline: both want
Suspense or a data library, which this app does not have yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread mobile/core.9043

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This shouldn't be commited

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS error on start-up: src/components/animated-icon.web.tsx(5,21): error TS2307: Cannot find module './animated-icon.module.css'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TS error on start-up: src/constants/theme.ts(6,8): error TS2882: Cannot find module or type declarations for side-effect import of '@/global.css'

@Jberma23

Jberma23 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Claude PR review:
Drafting a PR for the major fixes

Review: Expo mobile app + Rails JSON API

79 files · +14,179 −19 · 2 commits

I ran the full check suite locally rather than reading only:

Check Result
bin/rails test ✅ 15 runs, 42 assertions, 0 failures
bin/rubocop ✅ 31 files, no offenses
bin/brakeman --no-pager ✅ 0 security warnings
npx eslint . (mobile) ✅ clean
npm run typecheck (mobile) 2 errors on a fresh clone
npm audit (mobile) ⚠️ 11 moderate, one root advisory

The Rails half is ready to go, and the README and commit messages are unusually good — they explain why,
not just what. Two things block merge.


🔴 Blockers

1. mobile/core.9043 — a 12.8 MB crash dump is committed

ELF 64-bit LSB core file, x86-64, from '/home/psmith/worker-toolkit-alongwithyou/.../mobil'
execfn: '.../React Native DevTools-linux-x64/React Native DevTools'

A core dump from React Native DevTools crashing. It is 12.8 MB against a 3.41 MiB packed repo — it
dominates every clone, and it bakes an unrelated developer's home path into history.

mobile/.gitignore line 28 already has core.*, and the rule does match. It was force-added past it:

$ git check-ignore -v --no-index mobile/core.9043
mobile/.gitignore:28:core.*    mobile/core.9043

It entered in the first of the two commits, so a plain git rm in a follow-up leaves the blob in the
packfile forever. This needs an interactive rebase or git filter-repo before merge.

  • Remove from history, not just from HEAD

2. npm run typecheck fails on a fresh clone

src/components/animated-icon.web.tsx(5,21): error TS2307: Cannot find module './animated-icon.module.css'
src/constants/theme.ts(6,8): error TS2882: Cannot find module or type declarations for side-effect import
of '@/global.css'

tsconfig.json includes expo-env.d.ts, which supplies the CSS-module declarations — but that file is
generated by the Expo CLI and is gitignored (mobile/.gitignore line 10). I confirmed the diagnosis
by creating it and re-running: typecheck then passes clean. So the script this PR adds does not work until
someone has run npx expo start at least once.

Related, and it bit me live: the root README's Tests section says cd mobile && npx tsc --noEmit. With no
node_modules/.bin present, npx silently downloaded an unrelated tsc@2.0.4 from npm and printed "This
is not the tsc command you are looking for"
exit code 0, so that would pass in CI while checking
nothing.

  • Document the expo start prerequisite, or generate expo-env.d.ts as part of typecheck
  • README: npx tsc --noEmitnpm run typecheck

🟡 Should fix

3. No mobile job in CI

.github/workflows/ci.yml has scan_ruby, scan_js, lint, test, system-test — and zero references
to mobile, node, or npm. This PR adds ~1,100 lines of TypeScript and a typecheck script that
nothing ever runs. Note that scan_js runs bin/importmap audit, which covers Rails importmaps only, not
mobile/package-lock.json.

4. removeTask rolls back from a stale snapshot

mobile/src/app/tasks.tsx L74-83

const previous = items;                        // render-time snapshot
setItems((current) => current.filter(...));    // functional ✅
// ...
setItems(previous);                            // ❌ restores the whole stale array

toggleTask gets this right with functional updates throughout. Here, two quick deletes where the first
fails will resurrect the second deleted item.

5. JSON.parse is unguarded

mobile/src/lib/api.ts L95

const payload = body ? JSON.parse(body) : null;

Non-JSON responses throw a raw SyntaxError, which is not an ApiError, so every caller's cause instanceof ApiError ? cause.message : 'Something went wrong.' degrades to the generic string. The irony:
the most likely misconfiguration — Rails Host Authorization returning a 403 HTML page, exactly what
the config.hosts change in this PR addresses — lands in this path. Wrapping it and throwing an
ApiError with the status makes the one error people actually hit self-explanatory.

Also here: no fetch timeout, so an unreachable-but-not-refusing host spins forever.

6. reset-project now deletes real source

mobile/scripts/reset-project.js L14

const oldDirs = ["src", "scripts"];

Answering n runs fs.rm("src", { recursive: true, force: true }) — taking lib/api.ts,
app/tasks.tsx, everything. Harmless in the scaffold; not now. And src/app/index.tsx L54 actively
advertises it to the user as "Fresh start: npm run reset-project". Suggest deleting the script and
its package.json entry, as the script's own header comment invites.


🔵 Nits and discussion — 6 items

7. The identity restore missed two spots

The second commit was specifically about carrying app identity across, but:

  • mobile/src/components/app-tabs.web.tsx L61 — the web header still reads "Expo Starter"
  • mobile/src/components/app-tabs.tsx L26 — the Tasks tab uses tabIcons/home.png, the same icon as
    Home

8. The index ordering test cannot fail

test/controllers/api/v1/tasks_controller_test.rb L16. I checked the fixture timestamps directly:

pending:   2026-08-02 18:40:56.711953000 UTC
completed: 2026-08-02 18:40:56.711953000 UTC
IDENTICAL: true

Rails stamps all fixtures in one load with the same time, so body.map(...).sort.reverse == body.map(...)
holds for any ordering. Give the fixtures distinct explicit created_at values.

Relatedly, Task.newest_first (app/models/task.rb L6) orders on created_at alone, so ties are
non-deterministic. order(created_at: :desc, id: :desc) is the cheap fix, and it matters more once
pagination arrives.

9. mobile/LICENSE is Expo's, not ours

Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) — a scaffold leftover. There is no LICENSE at
the repo root, so the only license file in the project asserts Expo's copyright. Worth getting right
deliberately for a Ruby for Good project.

10. npm audit: 11 moderate, one root cause

All 11 trace to a single advisory — uuid missing buffer bounds check,
GHSA-w5hq-g745-h8pq — reached transitively through
@expo/config and the Expo CLI toolchain. Not fixable without Expo updating, and --force would
downgrade Expo. Fine to accept, worth knowing.

11. Scaffold screens remain

src/app/index.tsx ("Welcome to Expo") and explore.tsx ("This starter app includes example code") are
stock. Reasonable for a foundation PR — flagging only to confirm it is deliberate, since users would see
them.

12. No auth on the API

Every /api/v1 endpoint is world-readable and world-writable. The README's "Next steps" calls this out
honestly and with useful specifics (expo-secure-store over AsyncStorage, which is the right advice),
and Task is explicitly a throwaway. Fine as long as nothing deploys before auth lands.


📁 File-by-file walkthrough

Rails API — solid, nothing to change

  • app/controllers/api/base_controller.rbActionController::API for the right reasons, well
    documented. rescue_from for RecordNotFound and ParameterMissing; unprocessable returns both a
    banner message and per-field errors. Good shape for a client to consume.
  • app/controllers/api/v1/tasks_controller.rb — plain CRUD on Rails 8's params.expect. No
    pagination on index, which is fine at placeholder scale.
  • app/models/task.rb, db/migrate/20260802160512_create_tasks.rb, db/schema.rbnull: false on both columns with a default for completed; validations mirror the DB constraints.
  • config/routes.rb — namespaced api/v1, standard.
  • config/initializers/cors.rb — wildcard under Rails.env.local? only; production reads
    CORS_ORIGINS and safely defaults to an empty origin list if unset. Scoped to /api/*. The "CORS is
    a browser mechanism, native builds ignore it" note is the right thing to tell the next reader.
  • config/environments/development.rbconfig.hosts << /.*\.local(:\d+)?\z/. Anchored at the end
    with \z, so a suffix cannot widen it, and development-only.
  • test/ — 15 tests, all passing. Genuinely good coverage: JSON 404s, per-field 422s,
    unpermitted-attribute stripping, 400-on-empty-params, and the ParamsWrapper unwrapped-payload path. I
    had assumed that last test was wrong (I did not think ActionController::API included ParamsWrapper)
    and running the suite proved it does. Only test/fixtures/tasks.yml needs the timestamp fix from First #8.

Housekeeping — all well-justified

  • .rubocop.yml, config/brakeman.yml, .dockerignore — each excludes mobile/ with the
    same accurate rationale: node_modules vendors CocoaPods .podspec files that are Ruby and would be
    scanned as first-party. Verified: rubocop inspects 31 files, brakeman reports 0 errors.
  • .gitignore*:Zone.Identifier for WSL. Sensible for this team.
  • Gemfile / Gemfile.lock — one gem, rack-cors 3.0.0, checksum pinned.
  • db/seeds.rb — idempotent via if Task.none?.
  • README.md — the standout file. The per-target API URL table, the WSL2 netsh portproxy recipe,
    the "a tunnel routes the bundle, not your API" warning, and the EXPO_PUBLIC_* secrets caveat are all
    things normally learned by losing an afternoon. Only fix is the npx tsc line (Bump actions/upload-artifact from 4 to 7 #2).

Mobile app

  • src/lib/api.ts — deriving the API host from the Metro bundler address is the best idea in the PR
    and is well explained. The typed ApiError carrying status and fieldErrors mirrors the Rails contract
    exactly. See Bump selenium-webdriver from 4.45.0 to 4.46.0 #5.
  • src/app/tasks.tsx — clean optimistic updates. The comment about not touching state before the
    first await documents a real, non-obvious render cascade and earns its place. See Bump image_processing from 1.14.0 to 2.0.2 #4. Minor: Retry
    does not restore the spinner, so it gives no feedback, and toggleTask / removeTask do not clear a
    stale error on success the way addTask does.
  • src/app/_layout.tsx, index.tsx, explore.tsx — scaffold, see Add UI Library #11.
  • src/components/app-tabs.tsx / .web.tsx — native uses NativeTabs, web uses expo-router/ui
    with a custom bar. See Bump solid_cable from 4.0.0 to 4.0.2 #7.
  • src/hooks/use-color-scheme.web.ts — the inline eslint suppression is honest and correctly scoped;
    hydration state genuinely cannot be known during render.
  • constants/theme.ts, themed-text / themed-view, hint-row, external-link,
    web-badge, ui/collapsible, global.css — stock Expo template, unmodified.
  • package.json / app.json — identity correctly applied (Along With You, along-with-you,
    alongwithyou://). The engines.node >=20.19.4 bump has a real justification in the commit message.
    .env.example is well-commented and correctly warns that EXPO_PUBLIC_* is not secret.
  • .claude/settings.json, CLAUDE.md, AGENTS.md — three files of agent config. Harmless,
    just confirm the team wants them in-tree.

Summary: the Rails side is ready — tests, lint and Brakeman all green, and the API contract is
thoughtfully designed. Blocking: the committed core dump (needs a history rewrite, not just deletion) and
typecheck failing on a fresh clone. I would want #3-#6 before merge too; the rest is follow-up.

Notes on the formatting choices:

  • Nits and the file-by-file walkthrough are in
    Details blocks so the comment opens at a readable length
    but keeps everything.
  • Blockers have checkboxes so you can tick them off in-thread.
  • I left file references as inline code rather than permalinks, since I don't have the PR URL. If you want
    clickable line links, tell me the PR number and I can convert them to
    https://github.com///blob//path#L74 form.
  • gh isn't installed in this container, so I can't post it. Locally: gh pr comment --body-file
    pr-review.md.

@Jberma23

Jberma23 commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

fix for those blockers here PaulgSmith#1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants