Skip to content

iOS: fix bugs and logic issues found in the UX and functional reviews - #31

Merged
1395490241lhj-code merged 60 commits into
mainfrom
claude/brave-meitner-b5f1bf
Oct 21, 2026
Merged

1395490241lhj-code merged 60 commits into
mainfrom
claude/brave-meitner-b5f1bf

Conversation

@1395490241lhj-code

@1395490241lhj-code 1395490241lhj-code commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fixes from two read-only reviews of the native iOS app (a new-user UX review and a functional-logic review, 2026-10-19). Every finding was re-checked against the code by an independent verifier before it was fixed. The review fixes are one commit per item, so any single fix can be reverted on its own. Later commits stabilise UI tests on CI and add the pre-merge fixes below. 60 commits in total.

No SwiftData schema change. Legacy data migration now runs only once (de14ffd), so emptied lists stay empty. No sync flag, Keychain, entitlement, backup-format, login, timer or background-behaviour change.

Batch 1: dead ends and data safety (5ff9eac..46821a2, 8 commits)

  • AI 换一批 recipes are saved before being added to today; previously they couldn't be opened or cooked.
  • Home recommendations recompute once the real recipe library loads. Before, they stayed stuck on the 2 samples for the whole day.
  • Restock suggestions leave out the meal just cooked.
  • The consumption sheet shows unit-mismatched stock honestly.
  • Status staples flip back to in stock on stock-in.
  • A new weekly AI menu starts from today.
  • Special-plan delete and draft discard ask for confirmation, and the backup page warns that user recipes aren't included.

Batch 2: inventory deduction, reminders, data safety (829fdc2..cc1dd5a, 15 commits)

  • Swipe-delete buttons are red (inventory, planner, AI history).
  • Expiry reminders are no longer silently cancelled on every inventory change or cold start.
  • Mixed-unit batches: cooking only deducts the batches that convert, so 1 盒 is no longer zeroed against a recipe in 个.
  • Preset 葱姜蒜盐油 staples count as present.
  • The per-dish shopping sheet no longer under-buys. This fixes a regression from df25377.
  • A special-plan dish cooked from its recipe page deducts once.
  • Cooked meals are ignored in Home 还缺.
  • Recipe lines without an amount are judged by presence.
  • Weighed seasonings are classified as seasonings.
  • 添加常备食材 no longer overwrites another batch.
  • AI 做菜's unsaved 仅加入今日计划 is removed.
  • The weekly AI draft is persisted.
  • AI read tools honour per-message context exclusions.
  • The account-deletion page says what it clears.
  • Plan, weekly-menu and special-plan save failures are surfaced, and an unreadable special-plan table is guarded.

Test and docs (723ebeb, aeafe7c)

  • PlannerUITests.testCreateSpecialPlanFlowIsOneRequestPlusOneSwitch now accepts the draft-discard alert added in 46821a2.
  • AGENTS.md/CLAUDE.md: shared Mac resource rules (owner's edit).

Batch 3: cooking, recipe editing, planning, AI logic (45a0e62..2a57c56, 20 commits)

  • A library cook asks whether it is today's planned meal, which stops double deduction.
  • Deductions scale by servings ÷ base yield.
  • The edited recipe is shown after saving, so a second edit no longer overwrites the first.
  • Ingredient rows stay stable while editing.
  • Cooked dishes are kept when saving an AI special-plan menu.
  • Legacy data migrates once, so emptied lists stay empty.
  • Clearing ticked items says they are not stocked, and mark-all can be undone.
  • Deleting a recipe warns how many plans use it.
  • Edited 使用量 is written through.
  • Used-up is judged per food across batches.
  • Ticked rows count as bought and awaiting stock-in.
  • Quantity ranges and 条/棵/瓣/枚/粒/朵 are parsed.
  • The weekly menu's shopping goes through the shared review sheet.
  • A weekly menu is never regenerated or copied onto past days.
  • Unlabelled recipes are kept under the 难度/烹饪时间 filters.
  • Kitchen AI accepts a serving count, each AI page reopens its own conversation safely, and the AI knows today's date and names the day on plan changes.
  • A stuck shared link can be removed.
  • Staple reminders are delivered and denied toggles are shown as off.

CI follow-ups (c9f3998..e23e6a2, 12 commits)

  • UI-test stabilisation for CI (Xcode 26 / iOS 26), plus two fixes: an unplanned Planner day can be shorter than a planned meal (98cd920), and a completed meal shows its servings as text (fb2e567).

Pre-merge fixes (dd9ab14)

  • A weekly menu whose start date has passed now asks before it is added (「菜单日期已过」). The prompt names the start date and the new days, with years when the dates span two years. Agreeing moves the whole draft to start today, with dishes and order kept.
    • Cancelling writes nothing.
    • The collision check still runs on the moved days.
    • A pending or finished receipt is never moved.
    • If saving the moved draft fails, no meal is written.
  • Status-tracked staples (有货/快没了/缺货) are never deducted by cooking. Their quantity is a placeholder.
    • The consumption sheet shows them as present and says why.
    • With mixed stock, only the counted batches are deducted.
    • RecipeStockMatch and the D-050 undo contract are unchanged.
  • DEBUG-only fixtures:
    • injectable "now" for planner UI tests;
    • seeds for a stuck share link and for the consumption sheet. The share seed stops with a reason instead of falling back to the App Group queue or standard defaults.
    • AccountLifecycleFixtureDeletionService, so the account UI-test fixture never reaches the backend. Its preview is canned and every later step refuses.
  • Tests:
    • weekly stale-start unit tests (same day, days later, cancel, cross-week/month/year, DST, collision, persistence failure, pending/finished receipts, copy);
    • StatusStapleConsumptionTests;
    • a weekly stale-start UI test;
    • PR31VisualAcceptanceUITests, a reusable visual regression class over Light/Dark × Normal/AXXXL.

CI visual-test fixes (8a6e66e)

On CI (iOS 26.5), 3 of the 8 PR31VisualAcceptanceUITests failed in dispatch run 39611008239. The xcresult, activity timeline and screen recordings showed all three were faults in the tests, not in the app:

  • Appearance. The "Dark" runs rendered light, because the simulator appearance alone does not reach the app before its first render. The tests now pass UITEST_FORCE_DARK_APPEARANCE / UITEST_FORCE_LIGHT_APPEARANCE, like the other screenshot suites. The Dark screenshots from run 39611008239 are therefore not valid evidence.
  • Label queries. On iOS 26.5 a SwiftUI Label's identifier lands on its icon, which is never hittable. The recordings show the text fully on screen. Reachability is now judged on the label's text, and the identifier is only checked to still exist.
  • Short toast. The 1.8s 「已移除这条分享」 toast did show (the recording confirms it), but it left the accessibility tree about 1.9s after the tap. The first check came at 2.19s. A DEBUG-only UITEST_LONG_FEEDBACK_TOAST now holds Home's toast for 20s, as UndoToastTiming does for 撤销. Release builds have no DEBUG condition, and debug builds without the argument, keep 1.8s.
  • Local run of the full class on iOS 27.2: 8/8 passed.

CI visual-test fixes, part 2 (7514f3e)

Dispatch run 39637015717 on 8a6e66e passed 7 of 8. Its Dark screenshots are genuinely dark: mean luma 27–39, against 191+ for Light. testConsumptionSheetExplainsUnitsAndStatusStaples still failed at Light-AXXXL, for two more test faults:

  • Lazy rows. The identifier was checked before scrolling. On iOS 26.5 an off-screen list row is not in the accessibility tree yet. revealLabel now scrolls to the label's text first, then checks the identifier. The reachability, identifier and full-text assertions are all kept.
  • Locale order. The CI simulator runs in English, so localizedCompare sorted the rows 番茄, 葱, 鸡蛋. Scrolling down to 葱 left 番茄 above the screen. The visual tests now launch the app in its own zh-Hans / zh_CN, the order a member sees. Swiping back down was not used, because the sheet could be dismissed by it.
  • Local run of the full class with -testLanguage en -testRegion US, to reproduce CI: 8/8 passed, with rows in 葱, 番茄, 鸡蛋 order.

Validation for dd9ab14

Local runs used the reserved simulator (iPhone 18 Pro / iOS 27.2) through dev-sim-slot with the xcodebuild fallback, because Xcode MCP was held by another worktree.

  • Pre-fix negatives failed as expected.
    • Staples: 有货/快没了 葱 went from 1 to 0 and became unavailable.
    • Stale menu: 1 plan was written with no confirmation.
  • Full unit target, before the last two copy/fixture edits: 2,619 run, 0 failures, 5 skipped (gated hosted-sync smoke tests).
  • After those two edits:
    • WeeklyMenuMaterializationTests 51/51 and SharedImportQueueTests 15/15;
    • a temporary probe, not committed, confirmed that a refused defaults suite stops the share seed and writes no queue file;
    • the weekly stale-start UI test and the weekly + share-link visual cases passed.
  • Focused UI:
    • WeeklyMenuMaterializationUITests 16/16 and PlannerWeeklyHostUITests 4/4;
    • PlannerUITests, RecipeCookingModeUITests and SpecialPlanMenuUITests: the affected cases passed.
  • Visual acceptance: PR31VisualAcceptanceUITests covers 8 scenarios × 4 variants. All 78 screenshots were reviewed. Every new dialog is reachable with full text, and destructive actions are red.
  • Targeted CI on the previous head e23e6a2 (run 39540334113): GuestMergeSummaryUI5B2BB2AUITests 21/21. It shows the earlier ui-full (2) failure was a CI flake. It is not evidence for dd9ab14.
  • CI for dd9ab14:
    • iOS tests, push run 39599224175: success.
      • Unit target: 2,621 run, 0 failures, 5 skipped. This includes ConsumptionPersistenceTests 20/20 and KitchenStoreTests 49/49 (D-050), RecipePersistenceTests 13/13 and SwiftDataConsistencyTests 6/6 (de14ffd), StatusStapleConsumptionTests 7/7 and WeeklyMenuMaterializationTests 51/51.
      • Default UI set: 10 classes, 116 tests, 0 failures.
    • Node tests (Node 22 and 24) and iOS release config check: success.
    • Targeted PR31VisualAcceptanceUITests (dispatch run 39611008239): 3 of 8 failed. These were test faults, fixed in 8a6e66e (see above). Unit target 2,621 run, 0 failures.
  • CI for 8a6e66e:
    • Push run 39619771774: success. Unit 2,621 run, 0 failures; default UI 116, 0 failures; Node 22/24 and release check: success.
    • Targeted visual dispatch 39637015717: 7 of 8 passed; the remaining test fault is fixed in 7514f3e.
  • CI for 7514f3e (final head):
    • iOS tests, push run 39645705992: success. Unit target: 2,621 run, 0 failures, 5 skipped. This includes D-050 ConsumptionPersistenceTests 20/20 and KitchenStoreTests 49/49, and de14ffd RecipePersistenceTests 13/13 and SwiftDataConsistencyTests 6/6. Default UI set: 10 classes, 116 tests, 0 failures.
    • Node tests (Node 22 and 24) and iOS release config check: success.
    • Targeted PR31VisualAcceptanceUITests, dispatch run 39656183440 (iPhone 17 Pro, iOS 26.5): 8/8 passed. Unit target 2,621 run, 0 failures.
      • The run produced 76 screenshots: 19 states × 4 variants, none missing.
      • Dark screenshots: mean luma 11–54. Light: 191–244. Spot checks confirm the Dark variants really are dark.
      • The consumption sheet that failed before now shows its rows in 葱, 番茄, 鸡蛋 order, with full text at Light-AXXXL.

Earlier validation (before the first push, older tree)

  • Unit suite: 2,599 run, 0 failures, 5 skipped. Node source guards: 1,979/1,979.
  • UI regression (20 related classes, 249 tests): 248 passed, 1 failed. SpecialPlanMenuUITests.testReplacingAnOffScreenDishIsObservedByItsOwnRow fails the same way on the clean baseline 46821a2.

Not verified / known visual notes

  • No physical-device pass beyond an owner smoke install of batch 1.
  • CI runs Xcode 26.x / iOS 26.x, which is a different evidence class from the local iOS 27.2 runs.
  • At AXXXL, system alerts scroll their body text. In the stale-menu prompt the new range is below the fold, and the third button of 清除已购买 needs scrolling inside the alert. This is standard alert behaviour.
  • At AXXXL, the translucent toast lets the text behind it show through (shopping undo, removed share link). It needs a design decision and is not changed here.
  • The consumption sheet's 「来自:…」 line truncates at AXXXL. That line is unchanged from main.

Known follow-ups (not in this PR)

🤖 Generated with Claude Code

1395490241lhj-code and others added 30 commits October 19, 2026 15:13
…o today

An AI 换一批/search card is an in-memory recipe (id ai-<name>), so adding it to
today planned a recipe RecipeStore could not resolve: the plan could not be
opened, cooked or shopped for. Home and the recommendation browser now route
加入今天 through HomeRecommendationPlanning, which saves the dish via the
existing GeneratedRecipeMaterializer (reuse an identical recipe, else save a
new home-ai-<uuid> recipe) and re-points the card at it. The missing-recipe
placeholder no longer suggests that retrying helps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loads

Defaults computed from the two built-in samples before the online library
arrived stayed for the whole day. They are now replaced once when the source
switches to the real library, unless the member already searched, asked for
换一批, removed a card or browsed (moved off the first card, opened a
recommended recipe or the 更多推荐 page).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The post-cook restock suggestions ran before markPlanCooked, so the meal on
the stove still read as a pending plan and asked for its own ingredients
("未来 7 天计划需要"). RestockSuggestionEngine.generate takes the consumed
plan IDs and leaves them out of the planned-meal horizon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… sheet

Stock held as 1 份 against a recipe line of 2 个 rendered as 库存 - 个 /
使用量 0 / 扣减后剩 0 and silently deducted nothing. The row now shows the
stock in its own unit, says the units differ and is not deducted, and stays
deselected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merging a stock-in into a status-mode staple (盐/油/葱姜蒜) only added
quantity, so the shelf kept showing 缺货. The merge now resets the staple to
available; quantity-mode staples are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second AI 生成一周菜单 reused the previous menu's start date even after it
was added to the Planner or its days had passed. The form now shows the
target dates and starts from today (or the viewed week when it is ahead)
unless an unadded draft is still ahead; 重新生成 on the result page keeps
the menu's own dates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tities

Adding the same meal's ingredients again merged the full amount in a second
time. Shopping generation now subtracts what the pending list already holds
(same or convertible units only) and shows fully covered items as 已在清单.
Restock suggestions, the cook sheet's 补货建议 and Home's 还缺 row say
已在清单 instead of offering a second add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ps skip user recipes

删除计划 on a special plan now asks first, and leaving a special plan with an
unsaved AI menu draft asks before discarding it. The backup page and
清除全部本地数据 now warn prominently that kitchen backups do not include
user recipes, favorites or 常做 records (copy only; backup contract
unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The app-wide blue tint overrode the destructive role colour, so 删除 on
inventory rows, 移出计划 on planner meals and 删除 in Kitchen AI history
rendered blue. Tint them with AppTheme.danger, as Shopping already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The settings screen showed the 1-day and day-of lead times as on by
default, but KitchenStore read the unset keys as false, so every
inventory change and cold start rescheduled with no lead times and
cancelled every pending reminder. Read the keys with the UI defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A batch in another unit (1 盒 vs a recipe's 个) was deducted as if it
were in the recipe unit, so cooking could zero a whole box the sheet
never showed. Skip unconvertible batches and say so on the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Preset staples (葱姜蒜、盐、油) saved in status mode kept quantity 0, so
recipes, Home, shopping and AI treated them as missing while the shelf
said 有货. Sync quantity with the status (缺货 0, otherwise at least 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
df25377 subtracted every pending same-name row from a single dish's
needs, so a second dish was under-bought by what another dish added.
Shopping rows don't record their dish, so the per-dish sheet now keeps
the full amount selected and notes what is already on the list; the
all-meals views (restock, Home 已在清单) still subtract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening a special-plan dish's recipe dropped the dish identity, so
cooking there deducted as a direct recipe and ticking the dish later
deducted again. Carry the dish through to the confirmation so the
receipt is keyed to it (D-047).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Home's 还缺 row, readiness and its shopping list counted meals already
cooked, so it asked to rebuy what was just used. Use pending meals only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lines without a quantity were always listed as 需要购买 even when the
food was at home, and imported as "1 适量". Treat them as covered when
present, like Home, and import with unit 份.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Library lines like "盐 2 g" missed the seasoning lookup, so most curated
recipes always showed 还缺. Strip amounts with IngredientParser first and
add common Sichuan seasoning names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picking an existing row did not pass its id, and saving overwrote the
first same-name batch's quantity and expiry. Update the picked row;
without a pick, refuse instead of overwriting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It added a plan pointing at a recipe that was never saved, so today's
dish could not be opened or cooked. Keep 保存并加入计划 only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The draft lived only in memory until 加入用餐计划, so going back or
quitting lost it. Commit it right after generation, except over a menu
whose materialization is still pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Turning off 库存 for a message only skipped preloading; read tools still
read and sent inventory. Refuse excluded kinds for that turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page said the device could keep being used as a guest but did not
mention that local kitchen data is cleared. List it and suggest a backup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plan, weekly-menu and special-plan save failures set notices no view
read. Show them in Home module issues, and after a failed special-plan
load refuse whole-table writes that would delete the unreadable records.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since 46821a2 leaving a special plan with an unsaved AI menu draft asks
first, so testCreateSpecialPlanFlowIsOneRequestPlusOneSwitch tapped back
and never reached 用餐计划. Tap 放弃并返回 in the alert, then check the
plan itself is still listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md gains a "Shared Mac resources" section: per-agent fixed
simulators run through dev-sim-slot, no extra devices without approval,
and task temp-file cleanup and cache reuse. CLAUDE.md imports AGENTS.md
so Claude Code loads it directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLANNING-1. Cooking a dish from the recipe library while today's plan held the
same recipe deducted it as a direct recipe, and ticking the meal deducted it
again. 开始烹饪 from a page not opened for a plan now asks 这是今天计划里的那道吗?
when today has an uncooked meal of that exact recipe id; 是 cooks it as that
meal. No name matching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CONSUMPTION-1. Shopping and cooking mode scaled a meal by its servings, the
deduction stayed as written, and every row claimed the recipe had no base
yield. Deduction now uses RecipeQuantityScaler (planned: plannedServings, nil =
as written; direct: the chosen servings; Special Plan dishes stay as written per
D-047). The warning shows only when there is no base yield to convert from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RECIPES_LOGIC-9. The recipe page rendered and edited the value it was opened
with, so a saved edit did not show and a second edit overwrote the first. It now
reads RecipeStore.currentVersion(of:).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RECIPES_LOGIC-11. Rows were derived from the joined text with empty lines
dropped and identified by position, so emptying a row moved focus into the next
line. Rows now carry their own ids and are not trimmed while editing; blank
lines are still dropped on save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLANNING-8. Saving an AI 重新设计菜单 draft replaced the whole menu, dropping
cooked dishes (and their receipt link) and library-added dishes without a
word. Cooked dishes now stay with their ids, and saving over uncooked dishes
first lists what will be replaced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1395490241lhj-code and others added 22 commits October 19, 2026 22:18
SHOPPING_LOGIC-4. Ticked-but-not-stocked rows counted as neither stock nor
list, so generators asked to buy them again. They now count as listed
(已买「X」,待入库) with the same .inform/.subtract handling as pending rows, and
restock treats them as already on the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SHOPPING_LOGIC-5, RECIPES_LOGIC-6. "鸡蛋 2-3 个" became a food named "鸡蛋 2-"
and "蒜 3 瓣" kept the whole line as its name. A range between digits is one
quantity (its upper end, for buying), the common measure words are units, and
scaling a range scales both ends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SHOPPING_LOGIC-7. The weekly-menu page added the AI's list directly: foods with
any stock were dropped, "1,500" became 1 and repeated taps stacked. The button
now opens the same generator sheet as ⋯ → 生成购物清单.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PLANNING-5. 重新生成 kept a stale or already-added menu's start date, and
复制到 7 天后 copied the stored menu with no clamp. Both now follow the batch-1
"not before today" rule; the copy is of the menu on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RECIPES_LOGIC-12. Built-in recipes state no difficulty or time, so either
filter removed all of them. Unlabelled recipes are now kept, listed after the
matches, and the filter chip says 含未标注.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

AI_LOGIC-1. The server advertises plannedServings on propose_add_recipe_to_tonight
and propose_replace_planned_meal, but the interpreter refused any value, so
"今晚做红烧肉,4 个人吃" always failed. A whole number 1-12 is now carried and
applied; null is "not stated".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AI_LOGIC-7. Today and Planner share one controller; opening one page swapped
the other's conversation, cancelled its reply and dropped its pending change.
A page now reopens its own conversation on every appearance, but never while a
reply is generating or a change awaits 应用修改.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AI_LOGIC-4. The model was never told today's date, and 确认计划变更 rows did not
say which day they change. The system text now starts with today's local date
and planner preview rows start with the meal's day.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RECIPES_LOGIC-10, PERSISTENCE_LIFECYCLE-5. A shared link that failed or was
already imported came back and re-imported on every launch, with no way to
remove it. The share sheet gains 不再导入, and a share already shown and closed
waits for 开始识别 instead of auto-starting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PERSISTENCE_LIFECYCLE-7. Restock reminders fired one second after an in-app
change, while the app was in front, where iOS does not show them; and the
toggles stayed on after notifications were denied in iOS Settings. Reminders now
go out at the next 9:00 and the toggles read the system permission.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Empty commit: the [ui-all] marker makes ios-tests.yml run every UI test
class in four shards alongside the default unit, focused UI and
screenshot-tour job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…one [ui-all]

testARecognisableNameShowsNoHint checked .exists right after typing
卤鸡腿. The partial names typed on the way (卤, 卤鸡) may show the hint for
a moment, so on the slower CI simulator the check caught it before the
final name updated. Wait for it to disappear instead.

The [ui-all] marker reruns the full UI suite on the new head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two focused UI tests failed once on CI with code that had passed the
same tests earlier:
- ShoppingExperienceUITests.testEmptyStateUsesTheExistingAddShoppingSheet
  reopened the bulk menu while 75e4f3b's new 撤销 toast was expiring, and
  the 清除已购买 alert never appeared. Wait for the toast to appear and run
  out, and for the menu item, before tapping.
- HomeDashboardUITests.testHomePolishVisualStates read the 换一批 card's
  frame before the lazy shelf had built it. Keep swiping while it is
  missing and assert it exists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…all]

PlannerMealDeleteUITests.testContextMenuAlsoOffersRemove and
PlannerQuickCompleteUITests.testAXXXLLeadingEdgeKeepsBothActionsReachable
failed once on CI with app code that passed them in the previous run.
The row() helpers swipe the list until the row exists and return at
once, so the long press or swipe could land while the list was still
decelerating and only stop the scroll. Wait until the row is hittable
and its frame has stopped moving.

The [ui-all] marker reruns the full UI suite on the new head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UITEST_MERGE_LOADING slept 2 s and then fell through to a transport
error, so on the slower CI simulator the two loading-state tests in
GuestMergePreviewUI5B2BAUITests first queried after it had already
turned into the offline error. Hold the loading state for 600 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The system List minimum row height (52pt on iOS 26/27) beat the empty
day's 44pt whole-row target, so an unplanned day was exactly as tall as
a planned meal row and PlannerMealCreateUITests.
testUnplannedDaysWeighLessThanPlannedContent failed on every OS. Set
the Planner week List's defaultMinListRowHeight to the 44pt hit target.
Side effect: the week-range and 菜谱库 rows also go from 52pt to 44pt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PlannerMealEditUITests.testACompletedMealLocksServingsAndExplainsWhy
fails only on iOS 26.5 (CI): the servings Stepper's + reads enabled.
Report the element in the failure and check that tapping + leaves 2 人份
unchanged, to tell an accessibility-trait issue from a live Stepper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testEmptyPlanGeneratesADraftMenu looked for the loading state after the
1.5 s stub had already finished on CI. Add an opt-in
UITEST_SPECIAL_PLAN_AI_SLOW (6 s) used only by this test; other tests
keep 1.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testReplacingAnOffScreenDishIsObservedByItsOwnRow ignored the floating
tab bar, so its reveal drag started on the tab bar and left the plan
detail; record() then read a missing cell. End the viewport at the tab
bar and report an absent row instead of failing on its frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testProductionDarkAccessibilityMatrix's scroll helper began its drag
inside the 单位 field just above the keyboard, which on CI moved
nothing. Start the drag left of the field column, centred in the
viewport.

The [ui-all] marker reruns the full UI suite with the six pre-existing
red fixes (98c9fea..29bc739, cherry-picked) on the PR head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…epper

On iOS 26.5 (CI) the disabled Stepper's + still read as enabled, so
PlannerMealEditUITests.testACompletedMealLocksServingsAndExplainsWhy
failed and VoiceOver would offer a control that refuses the tap; the
probe in 49c79f5 showed tapping + left 2 人份 unchanged, so only the
trait was wrong. Render the fixed count as plain text, as the form
already does for the recipe row, and assert there is no +/− control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ui-all]

testCookingTimerCanStartAndCancelWithoutDebugUI tapped the timer as the
full-screen cooking cover was still sliding in, so on CI the menu never
opened and 1 分钟 was not found. Wait until the timer control is
hittable before tapping it.

The [ui-all] marker reruns the full UI suite on the new head.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1395490241lhj-code and others added 3 commits October 21, 2026 08:23
…tatus staples

- A weekly menu whose first day has passed now asks before it is added, then
  moves the whole draft to start today with its dishes and order kept.
  Cancelling writes nothing, the collision check still runs on the moved
  days, a pending or finished receipt is never moved, and if saving the moved
  draft fails no meal is written. The prompt names the passed start date and
  the new days, with years when they span two years.
- Status-tracked staples (有货/快没了/缺货) are never deducted by cooking: their
  quantity is a placeholder. The consumption sheet shows them as present and
  says why; mixed stock only deducts the counted batches. RecipeStockMatch and
  the D-050 undo contract are unchanged.
- DEBUG fixtures: injectable "now" for planner UI tests; stuck-share-link and
  consumption-review seeds (the share seed stops instead of falling back to
  the App Group queue or standard defaults); a deletion-service stand-in so
  the account fixture never reaches the backend.
- Tests: weekly stale-start unit and UI tests, StatusStapleConsumptionTests,
  and PR31VisualAcceptanceUITests (Light/Dark x Normal/AXXXL).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n PR31 visual tests

CI (iOS 26.5) failed 3 of the 8 PR31VisualAcceptanceUITests, and all three
were test faults:
- Dark runs rendered light: the simulator appearance alone does not reach
  the app before its first render. Pass UITEST_FORCE_DARK/LIGHT_APPEARANCE,
  as the other screenshot suites do.
- A SwiftUI Label's identifier landed on its icon, which is never hittable.
  Judge reachability by the label's text and only check the identifier
  still exists.
- The 1.8s removal toast was gone before the first check after the tap's
  idle wait. A DEBUG-only UITEST_LONG_FEEDBACK_TOAST holds Home's toast for
  20s, as UndoToastTiming does for 撤销; release and argument-less debug
  builds keep 1.8s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e in PR31 visual tests

CI (iOS 26.5) still failed testConsumptionSheetExplainsUnitsAndStatusStaples
at Light-AXXXL, for two test faults:
- The identifier was checked before scrolling, and an off-screen list row
  is not in the accessibility tree yet. revealLabel now scrolls to the
  label's text first, then checks the identifier; no assertion is dropped.
- The CI simulator runs in English, so localizedCompare sorted the rows
  番茄, 葱, 鸡蛋 and the test, scrolling down to 葱, left 番茄 above the
  screen. The visual tests now launch the app in its own zh-Hans / zh_CN,
  the order a member sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@1395490241lhj-code
1395490241lhj-code merged commit 45b108d into main Oct 21, 2026
12 checks passed
1395490241lhj-code added a commit that referenced this pull request Oct 22, 2026
Brings PR #31 (iOS review fixes) and PR #34 (Kitchen AI failure
diagnostics) under the Phase 1a work.

- test/ai-conversation-stream.test.mjs: both sides appended tests. Keep
  main's file whole (all 43 PR #34 tests, S1/S2 diagnostics included) and
  append this branch's servings test, rewritten for the protocol below.
- src/server/services/ai-conversation.js: keep plannedServings on
  propose_add_recipe_to_tonight and propose_replace_planned_meal. This
  branch had removed it because the old client refused it; PR #31's
  interpreter now accepts 1-12 and refuses anything else, so the schema
  offers the optional field with that range in its description. The
  low-risk wording for add-to-tonight is kept: the client executes it
  after validation without a confirmation step.
- The other shared iOS files merged without conflict.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant