Handoff: paused at Joshua's request to conserve usage. Changes are local and uncommitted; no deployment or release. Confirmation remains pending. Completed checks and next steps: revenue handoff.
ASC: iOS 2.5.11 WAITING_FOR_REVIEW; macOS 2.5.2 READY_FOR_DISTRIBUTION. Premium IAP com.heyitsmejosh.epiphany.premium is APPROVED. Older notes saying the IAP does not exist or the current submission is rejected are superseded.
Local payment fixes: failed storage reads/writes no longer report a successful unlock; IAP ownership is reserved atomically; Stripe waits for confirmed payment and retries failed entitlement writes. Paid feature enforcement still depends on EPIPHANY_REQUIRE_PRO=true. Verify the deployed flag and a real sandbox purchase before promoting the paid offer. See ../REVENUE.md.
https://epiphany.heyitsmejosh.com/api/auth?action=google returns a live 302 to
accounts.google.com using client_id 455155642136-...apps.googleusercontent.com, the same
Google Cloud client the shared Supabase project uses, with redirect_uri
https://epiphany.heyitsmejosh.com/api/auth?action=google-callback already configured.
So the "blocked on Joshua registering a Google app" note elsewhere is stale for Epiphany. The web half works today. What is actually missing for iOS/macOS:
- A Google button in
ios/Views/LoginSheet.swift+ the macOS equivalent, drivingASWebAuthenticationSession. - A custom URL scheme, Epiphany has none registered. Add
CFBundleURLTypesviaios/project.yml's generated info block, never the plist (xcodegen rewrites it). - A server change so the google-callback can redirect back to
epiphany://for the native client instead of only to the web app.
Do NOT re-add Sign in with Apple here. It was removed 2026-08-28 to clear the Guideline 2.1(a) rejection and 2.5.6 is in review on that basis; revisit only after a verdict.
Cloudflare Workers cap a single invocation at 50 subrequests. This handler had
THREE per-symbol fan-out paths: FMP quote+ratios (2 subrequests each), the v8 chart
fallback (1 each), and v10 fundamentals enrichment (1 each). The 63-symbol default
list issued well over 100, so everything past the cap failed -- including the Upstash
KV read inside getYahooCrumb, which is what finally showed up in wrangler tail as
[KV] get error: Too many subrequests by single Worker invocation.
A second, independent bug masked it: early returns dropped response bodies without
reading or cancelling them. Workers cap concurrent in-flight responses and an unread
body never completes, so the runtime cancelled the oldest and unrelated fetches failed.
Fixed with discardBody(); that removed the stall warnings and exposed the real error.
Fix: Yahoo v7 batch quote first (one subrequest for all 63 symbols), with the two
per-symbol paths gated behind PER_SYMBOL_FANOUT_MAX = 15. Verified in production:
/api/stocks returns 200 with 62 symbols.
Worth remembering: the thrown error said "Yahoo Finance v8 chart API returned no data",
which was misleading -- Yahoo was fine, the Worker had simply run out of subrequests.
Diagnose Workers 500s with wrangler tail before trusting the handler's own message.
- Not verified this session: iOS statement upload with a real PDF. The web path and all builds pass, but Josh's "haven't seen it work in months" on mobile was not reproduced or confirmed fixed. Do this first next session.
- Dashboard cleanup: four leftover empty draft submissions (5ed63ab5 returns 500 on item add, 5156cbcb, f88508a7; 036b9052 was reused for 2.5.8). They are not cancellable via API ("not in cancellable state"). Workaround when the concurrency limit bites: POST /v1/reviewSubmissionItems into one of them with
asc auth token --confirm, thenasc review submissions-submit --id. iOS 2.5.6 submitted 2026-08-28 WAITING_FOR_REVIEW (submission e55e6142). The 4 initial stale drafts were deleted, but retrying failed submits before that created 2 new drafts that remain. They are harmless but count toward Apple's concurrency limit (max 5). Dashboard-only cleanup via "Draft Submissions" panel. See [[reference_asc_stray_curvely_submission]] for the full concurrency trap. - Statement upload, two real bugs found and fixed 2026-08-10, awaiting Josh's retry to confirm they were the cause. The 08-06 Blob fix was genuinely incomplete; two independent second bugs existed, both of which produce exactly "the statement never landed":
- Unparseable PDF → 500, upload discarded (fixed, regression test added).
summarizeStatementBufferreturned{ spendingMonth: null }wheneverpdf-parsethrew, and the upload handler's dedupe then didspendingMonth.monthon that null →TypeError→ the outer catch returned a 500 withstatements: []. The PDF was already written to Blob at that point, so it was orphaned and never recorded in KV. Any statement whose PDF pdf-parse can't read (encrypted, or a newer Wealthsimple template) failed permanently and silently this way. Fixed at the source inserver/api/statements-data.js, falls back tosummarizeTransactions([], filename), so an unreadable statement is still stored and just gets named after its filename. Belt-and-braces?.inserver/api/statements.js. Test:tests/api/statements.test.js"still stores a statement whose PDF could not be parsed". - Advertised 25MB cap the transport cannot carry (fixed). The PDF travels base64-encoded inside a JSON body to a Vercel Serverless Function, whose request-body limit is 4.5MB, enforced by the platform, the handler never runs, so the client got a bare 413 with no useful message. Base64 inflates 4/3, so the honest PDF ceiling is 3MB, not the 25MB that
server/api/statements.js,ios/Views/PortfolioView.swiftandmacos/Views/PortfolioView.swiftall claimed (25MB was Blob's limit, which this path never gets to use). All three now say 3MB and reject oversized files client-side with a real message.
- Still needs Josh: retry the June + July 2026 uploads. If they now land, done. If they're over 3MB, the clear error will say so and the real fix is the upgrade path noted in
statements.js, client-direct upload to Blob (@vercel/blob/clienthandleUpload+ a token route), which bypasses the function body entirely. That's a cross-platform client change deserving its own session, not a constant.
- Unparseable PDF → 500, upload discarded (fixed, regression test added).
- SnapTrade billing: Josh believes it was paid, not independently re-verified this session (ran out of time/budget). Confirm via SnapTrade dashboard before trusting the "disabled key" urgent item above is stale.
- Follow-up on the above: the fallback is a publisher favicon, not a true per-article image. Genuinely per-article imagery needs og:image, which for Google-sourced items means resolving the opaque
CBMi...redirect then fetching the article page, ~2 network round-trips per row, too expensive to do inline in the handler. Only worth building if GDELT stays dead; check whether GDELT recovers first (it is rate-limiting shared Vercel IPs, "Please limit requests to one every 5 seconds"), since a healthy GDELT already supplies real images for free.
- iOS GitHub + Google sign-in: integrate native SDK flows. Web already ships hand-rolled GitHub + Google OAuth (server/api/auth.js:202-329, implemented 2026-07-09), no new web work needed. Existing Sign in with Apple (iOS native) stays as-is.
-
AppIcon-dark.png/AppIcon-tinted.pngare byte-identical copies of the light icon, not actually designed, cosmetic, tinted variant won't tint meaningfully. - Icon still reads generic/weak overall, a real personality refresh has been repeatedly deferred (imported from App Store.pdf 07-29, reconfirmed 08-04).
Confirmed 2026-08-06: iOS 2.5.4 is READY_FOR_SALE/live. macOS remains at 2.5.2 (statement-upload fix was iOS-only at the time; macOS parity landed separately 08-04, see above), bump macOS if the Mac statement UI work should ship as a version.
User confirmed live on-device it's not as fluid as native iOS Stocks, across three separate fix attempts (07-07, 07-07 later, 07-22, reverted, commit 69f82c6). Root cause not found. Do NOT attempt a 4th blind fix.
- Attempt 1: moved
.frame(height:)off per-drag resizing to a fixed max-height + outer clip animation. Real perf issue, not the (or not the only) felt cause. - Attempt 3: replaced per-drag resize with
.offset(y:)compositor transform (textbook fix for this SwiftUI pattern), broke drag hit-testing (touch passed through to the row underneath instead of moving the drawer). Reverted. Suspected cause: the drag handle's.overlay(alignment: .top)is attached to the offset-shifted view, whose own layout frame stays fixed size, something in the offset→padding→overlay→clipped chain desyncs the touch target from the rendered position. - Before attempting again: (1) get Josh to describe specifically what feels wrong (lag following finger / jank on release / stutter during momentum / frame drops with images loading), (2) profile with Instruments Time Profiler on a real device, none of the 3 attempts have done either. Consider testing the offset approach on an isolated dummy view first, or attaching
DragGestureto a stable sibling view instead of the offset-shifted card.
- Old orphaned "Epiphany Mac" app record (6782703473, bundle
com.heyitsmejosh.epiphany-macos) needs Joshua's manual ASC dashboard deletion, no public API to delete an app record (confirmed on Talli's/Voxprint's equivalent orphans too). Do not upload anything further to it. Full merge history/blockers (upload error 90348, three widget compile fixes) preserved in CLAUDE.md.
- Brand identity pass (raised by Josh, not started), iOS/macOS/web lean entirely on default Apple styling (
Palette.appleBlue, SF Symbols only), no signature color/typography beyond the landing page's Fraunces headline. Ideas: one signature accent color app-wide (no gradients/purple), reuse Fraunces for in-app section headers, a custom map style/pin set (biggest available differentiation surface now that native POI pins are off). Needs its own dedicated design session. - "Coolest app ever", explore an unnamed app/website's idea and integrate it (from Coolest app ever.pdf). Underspecified, ask Joshua which app/site before scoping.
- Landing page screenshots must use a real populated account, not a demo account (currently renders an empty portfolio, looks broken on the marketing site).
- Fresh App Store screenshots (fastlane snapshot erroring, exit 75), optional, 2.5.1 shots still carry over fine.
- iOS/macOS mirror of web's commodity/crypto stats grid (dayHigh/dayLow/prevClose, high24h/low24h, backend shipped 07-19). Bigger than a mirror pass: no iOS/macOS view renders
CommodityData/CryptoDataat all yet (StockDetailView.swiftis equity-only), needs a dedicated session to build the detail view first, then add stats fields. - Derivable without new backend work: Period High/Low from already-loaded price history; SMA20/EMA50 overlays on the commodity chart (client-side,
StockDetailViewalready computes these for equities, reuse). Needs a chart refactor pass. - Mirror Yahoo Finance layout: at drawer
.large, the ticker bar should own ~top 10% of the view, visual-proportion tuning, do live on the sim.
- IBKR broker adapter, blocked on IBKR account approval: Joshua started an Interactive Brokers Canada individual application on 2026-09-06 and is filling KYC forms. Once approved, write
server/api/broker/ibkr.jsmirroring the existingalpaca.jsstub interface (paper account endpoint, execute trade with symbol/qty/side). Wire it intobroker/morning-run.js:runLive()in place of the Alpaca call. Alpaca is blocked because it rejects Canadian residents. IBKR is the funded path forward for live autopilot trading in Canada. - Evaluate replacing SnapTrade with Interactive Brokers, SnapTrade is read-only by design (blocks live trading, forced
AutopilotSection()to be commented out); IBKR Web API would unblock it. Needs scoping: auth model, holdings/positions endpoints, cost, SnapTrade sync-layer reuse. (The SnapTrade billing outage above is a good moment to revisit this.) - Live trading blocked on a trade-permissioned brokerage, needs Joshua:
sign up for Alpaca (paper account, free, fastest path) at alpaca.markets. ALPACA BLOCKED: rejects Canadian residents, hides paper accounts behind US application. IBKR only if TSX/Canadian equities needed later. RBC confirmed no public trading API. Researched 2026-07-22: IBKR does not bridge to Wealthsimple; the only way to execute trades in a Wealthsimple account is unofficial reverse-engineered wrappers that violate WS's ToS, not viable. Real path is a separate IBKR/Alpaca account, SnapTrade/WS stay read-only for display only. 2026-09-03: unlocked live-mode plumbing ahead of the broker swap --autopilot.jsacceptsmode:'live'from the client (was hardcoded to paper),morning-run.jsruns the full watchlist in live mode (was BTC-only probe) with a hard $50/trade cap + 20-trade total cap then auto-revert to paper,TradeWorkflow.jsxhas a Paper/Live toggle. Still wired to SnapTrade under the hood, which is a no-op for real fills against Wealthsimple (see above) -- swap the execution call inrunLive()to IBKR once the adapter exists and the account is approved, nothing else in this plumbing needs to change. - BLOCKED (Joshua): "Login with TradingView" to sync watchlist, no public TradingView API for reading a user's watchlist/account. Receiving endpoint already exists (
server/api/broker/webhook.jsaccepts{ticker, action, qty}, places Alpaca paper orders), only remaining step needs Joshua's TradingView Pro+ account to configure an outbound webhook alert. Can't self-provision. - Needs Joshua: re-enable the disabled Trade tab in
FinancePanel.jsxonce he eyeballs a real force-sync confirming holdings/math are clean (phantom-holdings dedupe shipped 07-02). - Autopilot trading UI visibility, ambiguous which control this refers to (reconfirmed 07-25: autopilot is fully implemented, paper mode works, only live execution is blocked on brokerage permission, nothing is visibly non-functional). Needs Joshua to name the specific control before hiding anything.
- Autopilot copy ("Pilots"): curated famous-investor model portfolios (congress trades + 13F trackers, read-only vs SnapTrade) with performance-vs-you + new-trade alerts; web first, mirror iOS. Feature notes in wiki
pages/epiphany.md. -
src/App.jsxASSETSconst (~180 tickers, fallback-only) and the Watchlist, both tightly coupled to the 978-line trading simulator, used in 6+ places. Not safe to blind-edit; needs its own dedicated session for both together.
- Markets-row buy/sell/hold badge on the list view, the "why" panel itself already ships (tap BUY/SELL/HOLD pill on
StockDetail.jsxfor reasons + math rationale). Putting it on each Markets-list row is blocked:MarketRowonly gets symbol/name/price/changePercent, butsignal()needs 35+ price points, needs either N per-row history calls or the bulk price-history endpoint already deferred for sparklines. Same blocker, fix together. - Portfolio/Settings tab audit, getting cluttered, decide what stays. Calendar view specifically flagged as possibly unnecessary.
- iOS landscape support, one-line flag, but map/markets/portfolio are portrait-first; enabling without adapting layouts looks broken. Needs a per-screen pass (split layouts, wider charts, map controls). Same applies to other iOS apps. Also: Holdings "Display metric" row needs per-holding day-change data from backend (model only has marketValue + gainLoss).
- App Store splash/screenshot refresh: current screenshots show demo data, portfolio reads $0. Regenerate with realistic seeded portfolio data (fastlane snapshot / appstore-screenshots skill) before next submission.
Resolution: The initial investigation tracked the empty-state issue (Portfolio $0.00, "No transaction data", "No budget data") to the demo account lacking KV seed data, and proposed a dead-end fix approach (manually author demo portfolio JSON). The real solution turned out simpler: the snapshot pipeline was logging into the empty demo account, but the repo's gitignored .env.accounts.local file already contained real account credentials. Fixed by running fastlane snapshot with DEV_EMAIL/DEV_PASSWORD pointing to Joshua's real account. Uncovered two pipeline bugs in the process: (1) ios/fastlane/Snapfile was missing -skipPackagePluginValidation, causing the SwiftLint SPM build-tool plugin to fail headlessly and timeout (~15s failures, ~8 silent retries per full run); (2) PreviewScreenshot.swift launched the app three times (Portfolio, Settings, Settings again), and the third launch reliably died with "Simulator device failed to launch" timeout, consolidated Settings into Portfolio launch to reduce to two launches, eliminating the timeout. Result: four refreshed screenshots deployed live showing real portfolio data ($162.37, actual holdings, spending chart). Settings screenshot deliberately omitted to keep personal email off the public landing page. Unreferenced PNG duplicates deleted from public/screenshots/. Commit f6b08f8.
- Needs Joshua: re-upload the June 2026 statement. Consequence of the month-labelling
bug above, because July was mislabelled "Jun", the upload handler's dedupe treated it as a
duplicate of June and replaced the June record in KV. The parser fix stops it recurring and
refreshStoredStatementsself-heals stored records belowSUMMARY_VERSION(4), but it cannot recover a record that was overwritten. If the June PDF still exists in Blob it may survive as an orphan; otherwise just re-upload June, then July, and confirm both now appear as separate months. - iOS: fix "Login with …" (social sign-in buttons), blocked, not a code bug.
server/api/auth.jsalready has complete hand-rolled Google (:202-268) and Facebook (:270-329) flows;GOOGLE_CLIENT_ID/SECRETare present but empty andFACEBOOK_CLIENT_ID/SECRETare unset. Needs Joshua to register the apps in Google Cloud Console / Meta for Developers, then set the Vercel env vars. No code change unblocks this. - Web landing page needs a light mode (looks great otherwise), note
CLAUDE.md's standing rule "Web: dark only (Gotham brand, hardcoded dark surfaces)"; this item contradicts it, so confirm the rule is being retired before implementing. - Mac app: thorough end-to-end test pass
Resume note (2026-08-11), updated 2026-08-13: the
wip: partial work from /work notes ingestcommit (2a46fe9) has now been reviewed and verified, 413 tests pass, iOS and macOS both BUILD SUCCEEDED. Five of the items above are genuinely done and checked off. The "unpushed" claim in the original note was already stale:2a46fe9andd98e8ceare both onorigin/main. Safe to build on.
- Stock view: add live mode (seconds-level timeframe, not just minute)
- Make the "Buy/sell/hold" button clickable → opens a drawer of sources
- Add themes (dark mode etc.) to the stocks view
- Analyze drawer video at ~/Documents/Misc/epiphany.mp4 for the drawer UX spec
- Analyze project from CLAUDE.md + README.md, then refresh the app icon based on that analysis
- Widget support on iOS and macOS. Both platforms, one pass.
Batch of 6 was dispatched; items 1–2 shipped (see checked lines above), 3–6 stopped at 69% session usage rather than half-built. All four are still open exactly as written above , this section only records what was learned while scoping them, so the next pass doesn't re-derive it.
- Period High/Low + SMA20/EMA50 on the commodity chart (roadmap line ~48). Confirmed
still the right shape: purely client-side off already-loaded history, no backend work.
StockDetailViewalready computes SMA/EMA for equities, the job is reusing that path for commodities, which the existing line correctly calls a chart refactor. Note the EMA indicator toggle changed colour in the palette pass (Palette.slatenow, wasPalette.purple), match new overlays to the current tokens, don't reintroduce a literal. - Buy/sell/hold button → drawer of sources (roadmap line ~92). The "why" panel it
should reuse already ships on
StockDetail.jsx(tap the BUY/SELL/HOLD pill → reasons + math rationale from WHITEPAPER.md). This is a wiring job, not a new panel. - Markets-row buy/sell/hold badge (roadmap line ~62). Re-read the existing note before
attempting: it is blocked, not merely unstarted.
MarketRowonly receives symbol/name/price/changePercent butsignal()needs 35+ price points, so it needs either N per-row history calls or the bulk price-history endpoint already deferred for sparklines. Same blocker as the sparkline item, fix them together or not at all. - Themes for the stocks view (roadmap line ~93). Native only.
CLAUDE.md's standing rule is "Web: dark only (Gotham brand, hardcoded dark surfaces)", so this must not turn into relighting the web app. NativePaletteis already fully adaptive light/dark, so the real question is what a "theme" adds beyond system appearance, needs Joshua's intent before building.
- Venue reviews via Google Places, DEFERRED 2026-09-07 (Joshua: later). Yelp key is dead (400) and every reviews API (Yelp, Google Places, Foursquare, TripAdvisor) requires a credit card on file even for the free tier. Needs Joshua to add a card + accept ToS at console.cloud.google.com (project winnie-372220, Places API enable started). Then: create key,
wrangler secret put GOOGLE_PLACES_API_KEY, portserver/api/venue-details.jsfrom Yelp to Places (New)./api/venue-detailsstays{available:false}. If photos alone are wanted later, Wikimedia Commons geosearch is keyless. - Google reviews: folded into the item above.
- Predictions feature: integrate or build prediction markets in the spirit of Wealthsimple Predict / Polymarket / Kalshi. Decide integrate-vs-build; check each for a public API.
- Social layer copying Loopt (friend map / presence). Reference: https://youtu.be/KhhId_WG7RA?si=c0cu-aQHq087KJeF
Splash/hero background is a static image preview of the app. Replace with a live, non-interactive demo of the real app rendering behind the hero (pointer-events:none, demo/mock data, no auth). After sign-up, the same view becomes the real interactive app and the marketing chrome (Log in / Get Epiphany / Get started) hides. Scope: large UI change, do in a dedicated session.
- Add a police scanner feature.
- Build upon the ontology / people index feature.
- Add Moderna and Pfizer to the stock list, plus any other suggestions. "Theoretically an infinite list that the user can organize and categorize themselves", i.e. make the stock list user-extensible and user-categorizable rather than a fixed set.
- Portfolio syncing is laggy and unreliable, "It's working but not reliably, or very fast. I deposited into my chequing account, Epiphany took days to reflect it." Investigate sync freshness/latency end to end.
-
Mobile web UI/UX is spotty overall, general pass needed.
-
/api/macroreturns an empty set on Cloudflare Workers. FRED sits behind Akamai, which refuses Workers egress IPs, every series comes back 520 at the edge, while the identical fetch succeeds from Vercel and from a laptop. Browser User-Agent, Referer and Accept-Language were all tried and none help, so it is the egress IP and not the request shape. Fix is either a reachable macro source or populating KV from something that can reach FRED. Already fixed alongside it: the handler used to cache the empty result for a full hour, serving the outage from memory in 1ms. -
Move the cron jobs off Vercel.
broker/morning-runplaces real trades, so Cloudflare crons stay disarmed inwrangler.jsoncwhile Vercel still runs them on schedule, arming both would double-trade. Web traffic is already fully on Cloudflare; this is the last piece of that migration. -
Ship the macOS avatar fix.
macos/Models/AppState.swiftdecodes inline data: URL avatars now, but macOS is still live on 2.5.2 and the change is unshipped. -
/api/cronhits the Workers subrequest cap. The first Cloudflare-run cron (2026-08-26 08:00 UTC) completed 200, but logged[KV] set error: Too many subrequests by single Worker invocationtwice, so part of the cache write was dropped. The handler fans out to 111 stock symbols plus markets and commodities, each its own fetch, then writes several KV keys, that is well past the per-invocation subrequest limit. It never showed on Vercel, which has no such cap. Fix is to batch the Yahoo fetches or split the run across the three cron slots; raising the limit is a plan change, not a config flag. Also visible in the same run and probably older:Cron crypto fetch failed: HTTP 403.
- Remaining empty map layers (as of 2026-08-27, now dated):
/api/events502 (GDELT unavailable, no cache),/api/flightsFIXED (see above),traffic/emergency/weather-alerts/aqi/earthquakesall 0 (unchanged).crimereturning 0 is expected, it only covers 9 US cities. Working:news(69 articles),wildfires(3). - "One big dot for the entire city" on web: not yet root-caused. With almost every layer empty, the surviving markers cluster into a single badge (
clusterPointsinsrc/components/LiveMapBackdrop.jsx, radius 60 at initial zoom 10.6). Re-check now that flights layer is returning real data. - Loading glitch in the top App Store bar on web.
- Bump landing page links and GitHub links to current.
- Decide: is the app icon green or blue? Pick one and make it consistent.
- People indexer does not work. Expand it and add AI support (Qwen etc).
- Housekeeping: build
202608271355(2.5.6) was uploaded by mistake on 2026-08-27, a duplicate of work already done. The build actually in review is 2.5.5 /202608271349, which does include the dead-button removal (03c89b3).ios/project.ymlnow reads 2.5.6, so the next build is correctly numbered; the orphaned 202608271355 build can be ignored or deleted in ASC. Note44cfa65(broker webhook auth) landed AFTER that build, so it ships server-side only until the next binary.
- Rotate the two dashboard-gated secrets (2026-08-27 security sweep). Fresh
WEBHOOK_SECRET+CRON_SECRETwere already regenerated locally in.env.tui.localand need pushing to Vercel prod (my broker/webhook + autopilot cron endpoints now 503 until the secret is set in prod). Still to do by hand, because no API mints these headlessly:STRIPE_SECRET_KEY(rollsk_live_at dashboard.stripe.com/apikeys)SUPABASE_SERVICE_ROLE_KEY(Supabase → Settings → API; rotating invalidates all tokens) Runscripts/rotate-keys.shto open all tabs, thenscripts/sync-vercel-env.sh.
- Epiphany 2.5.5 iOS REJECTED, reason now READ (submission
1afd5ca2-4103-4da7-914f-fca3f2051915, read 2026-08-28). TWO guidelines cited, neither resolved. - 2.1(a), "error shown when attempting to sign in with Apple", reviewed on iPad Air 11-inch (M3), iPadOS 26.6. The reviewer screenshot shows "Sign-Up Not Completed" rendered inside Apple's OWN Sign in with Apple sheet while creating an account forar_user1144@icloud.com. It never reaches our backend, so_apple-jwt.jsis definitively not implicated, that earlier audit was correct. - The entitlement hypothesis is RULED OUT. Verified end to end 2026-08-28:ios/project.ymldeclarescom.apple.developer.applesigninunderentitlements.properties(fix39d7102, 2026-07-27) and it survivesxcodegen generate;ios/Epiphany.entitlementshas it; the archive that produced the reviewed build (.asc/artifacts/Epiphany.xcarchive, CFBundleVersion202608271349, 2.5.5) is signed with it; its embedded profile grants it; the other distribution-signed IPA from the same day (202608271355) retains it through export, so export is not stripping it; App IDcom.heyitsmejosh.epiphany(8QHAV87C9U) hasAPPLE_ID_AUTH/PRIMARY_APP_CONSENTand no competing App ID claims Apple auth; client code is stockSignInWithAppleButton. - Closed 2026-09-07: Sign in with Apple was removed 2026-08-28 and the 2.5.7 review raised only 2.1(b), so 2.1(a) no longer applies. Keep the iPad note below only if Apple sign-in is ever re-added. - The binary isUIDeviceFamily = [1](iPhone-only) yet review ran on iPad, and Apple's letter adds "apps that may be downloaded onto iPad devices should function as expected for iPad users." Next step: run 2.5.5 on an iPad in iPhone-compatibility mode and attempt Sign in with Apple. Do NOT resubmit until it reproduces. Builds202608271349and202608271355are both VALID and attachable. - 2.1(b), Information Needed (business model), 4 questions. Draft answers + evidence innotes/2-1-b-business-model-reply.md. Surfaces a real Guideline 3.1.1 exposure needing Joshua's decision before replying: Autopilot live trading, Daily Brief and the People graph are gated byisPro, obtainable only via a one-time Stripe payment on the web, andios/API/EpiphanyAPI.swiftcalls every one of those gated endpoints.asc iap list --app 6779522175returns zero IAPs, andios/Services/StoreKitManager.swift(productcom.heyitsmejosh.epiphany.paid) is dead code referenced nowhere. The iOS app therefore unlocks paid features bought outside IAP. - Widgets receive no data:
widgets-ios/Models/WidgetAPI.swiftreadsUserDefaults(suiteName: "group.com.heyitsmejosh.epiphany"), but the main app declares no app-groups entitlement (only the widget target does) and no main-app code writes that suite. Found while inspecting the signed archive; unrelated to the rejection.
- Verify the deploy state before replying to Apple's 2.1(b) query. The answer to question 4 in
notes/2-1-b-business-model-reply.md("none") is only true while this build is live, check withnpx vercel inspect https://epiphany.heyitsmejosh.comand confirmcreatedis newer than the gates.js commit. Do NOT verify by curling/api/daily-briefor/api/peopleunauthenticated: both return 402 under old and new code, so that probe proves nothing.
This whole section was stale: the migration described below as blocked/not-started actually
shipped 2026-08-25/26 (2e6b6c3 port to Workers, a2a1547 custom domain cutover, cb72eef
crons moved, e7ca627 Vercel remnants cleaned up, dcbfd3a 2026-08-31 Stripe webhook fix
post-migration). wrangler.jsonc serves epiphany.heyitsmejosh.com directly via Workers
custom domain, KV backs the old Blob usage (server/api/_blob.js), crons run on Cloudflare
triggers. vercel.json is a dead leftover, safe to delete once double-checked nothing still
reads it. Deploy is manual: npm run build && npx wrangler deploy -- there is no
GitHub Actions auto-deploy on push (.github/workflows/test.yml only runs tests), so a
merged PR does not go live by itself.
Confirmed still true from the original scoping: whoever revisits this should re-verify
pdf-parse (bank-statement parsing) actually works under the deployed Worker with a real
upload, since that was the one open risk flagged before the migration and no test result for
it is recorded here.
- Budget card: "Avg Monthly Spending" and "Monthly Surplus" show budget targets (1070) because
financeData.spendingis empty in KV. Make them use the same statement-derivedactualsthe category pie uses (ios/Views/PortfolioView.swift ~L272averageMonthlySpendingvs ~L780actuals). Mirror on macOS. - Markets list: add a Buy/Hold/Sell group filter using
Indicators.TradeSignal(ios/Helpers/Indicators.swift L75); needs per-symbol history so compute lazily/cached. Default sort is already% Change(MarketsView.swift L12). - Markets toolbar: search + filter glyphs render as semi-transparent overlay on top of rows (ios/Views/MarketsView.swift ~L212
toolbarGlyph); give them an opaque pill or move into the nav bar. - Portfolio pie: "Other" is 64% because transactions with nil category fall into "Other" (PortfolioView.swift L787). Bucket by merchant when category is nil, cap slices at top 6 + Other, and add a category filter chip row.
- Current location button works but no pin shown on map
- Event/places need more detail (reviews etc.)