Skip to content

fix(mobile): keep code reviewer instruction drafts and grow the editor field - #7892

Merged
iscekic merged 3 commits into
mainfrom
drain/code-reviewer-instructions-draft-guard
Oct 22, 2026
Merged

iscekic merged 3 commits into
mainfrom
drain/code-reviewer-instructions-draft-guard

Conversation

@iscekic

@iscekic iscekic commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Change

apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx:

  1. Growing field — the multiline Custom instructions TextInput used a fixed h-32. A stored value longer than the box opened scrolled to the caret with the first line clipped and no scroll indicator. It now uses min-h-32 (same pattern as the multiline input in save-profile-sheet.tsx), so the field grows with content and opens at the first line.
  2. Draft guard — the draft lived in a ref over an uncontrolled defaultValue with no back guard, so Android back while editing silently restored the stored text and discarded the rewrite with no confirmation, undo, or warning. The route now wires the shared useSettingsBackGuard (the same hook the security-agent settings screens use), so every way off the screen (header back, Android hardware back, iOS swipe-back) offers Keep editing / Discard / Save while the draft differs from the stored value. An Android back that only dismisses the keyboard never removes the screen, so the edit is kept. The bottom Save button sets skipNextGuardRef before its own router.back() (same bypass as SettingsSaveButton).

The draft stays uncontrolled per apps/mobile/AGENTS.md "Text inputs": the loaded value reaches the field directly, the dirty baseline and the draft live in refs (seeded once, so later refetches never clobber in-progress edits), and only the derived dirty flag reaches state.

Tests

New mounted suite instructions.mounted.test.tsx keeps the real useSettingsBackGuard / useConfirmDialog chain and stubs only the native surfaces:

  • the field renders min-h-32 (no h-32), multiline, uncontrolled with defaultValue from the stored text;
  • a clean draft backs out with no confirmation;
  • a dirty draft: back fires the confirm dialog with Keep editing / Discard / Save changes; Keep editing keeps the draft and the screen; Discard replays the navigation; the dialog's Save persists the trimmed draft and leaves;
  • the bottom Save persists the trimmed draft and bypasses the guard for its own back;
  • a query refetch while editing does not clobber the draft (guard stays armed);
  • loading still renders the skeleton with no editor.

pnpm test (apps/mobile): 1191 files, 17878 tests pass. tsgo --noEmit reports no errors in the changed files (the 963 pre-existing errors on this checkout are identical with the change stashed and all live in unrelated files). oxlint (mobile config) clean on both files.

End-to-end verification (Android emulator, local dev stack)

Local stack: pnpm dev:start (nextjs on 3000, Metro on 8081), fake-login user seeded, a six-line stored instructions value for the personal GitHub reviewer, dev client installed from the mobile-native-build artifact, JS served from Metro.

Observed on device (emulator-5554, Android 16):

  • Growth (finding 1): with six Arabic lines stored (the finding's shape, taller than 128dp), the editor field rendered 392 px ≈ 149 dp — grown past the 128 dp min-h-32 floor (the old fixed box was 336 px), scrollable=false, first line السطر الأول: … fully visible at the top, all six lines readable without scrolling. Screenshots: /tmp/arabic-editor-final.png, /tmp/arabic-editor.png.
  • Draft kept on keyboard-dismiss back (finding 2): tap the field (selection popup), press Android back → the screen stays and the edit remains in the field.
  • Guard: back again → the "Unsaved changes" sheet appears with Keep editing / Discard / Save changes. Keep editing keeps the edited draft. Discard leaves without saving (verified in the DB: the edit never persisted). Dialog Save persisted the trimmed draft (mutateAsync payload verified in Postgres) and returned to the platform screen.
  • Clean back: with no edits, back navigates immediately with no dialog.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Dirty check: How should the screen classify dirty for the instructions draft?
    Answer: Trimmed comparison (Recommended)
    Confidence: 0.75 · Precedent: Autonomy: make executive decisions yourself, with "is this reasonable?" as the test. The trimmed comparison matches what Save persists and avoids a stale dirty flag right after saving.
  2. Question: Validation: Should the guard ever treat the instructions draft as invalid?
    Answer: valid = true always (Recommended)
    Confidence: 0.85 · Precedent: The "is this reasonable?" test. An empty instructions value is a legitimate save that clears the instructions, so blocking Save would be wrong.
  3. Question: Dialog copy: Which copy should the unsaved-changes dialog use?
    Answer: Existing settingsSave copy (Recommended)
    Confidence: 0.70 · Precedent: Do the least amount of work with no drop in quality; keep similar elements consistent across surfaces.
  4. Question: Save button: The screen currently saves via a bottom Button and then router.back(). How should saving work with the guard?
    Answer: Keep bottom Save, no header Save (Recommended)
    Confidence: 0.65 · Precedent: Cut unnecessary affordances; remove redundancy. Two save affordances on one small screen are redundant.
  5. Question: Field height: How should the multiline instructions field size itself?
    Answer: Grow with content (Recommended)
    Confidence: 0.92 · Precedent: Fix the root cause and the class of the issue; the brief asks to replace h-32 with a minimum height.

…r field

The Custom instructions editor on the code reviewer platform screen had two
problems with a multi-line stored value:

1. The multiline field used a fixed h-32, so a stored value longer than the
   box opened scrolled to the caret with the first line clipped and no
   scroll indicator. The field now uses min-h-32 and grows with content,
   so it opens at the first line.
2. The draft lived in a ref over an uncontrolled defaultValue with no back
   guard, so Android back while editing silently restored the stored text
   and discarded the rewrite with no confirmation. The route now wires the
   shared useSettingsBackGuard (same hook as the security-agent settings
   screens): any exit while dirty offers Keep editing, Discard, and Save;
   an Android back that only dismisses the keyboard still keeps the draft,
   and the save button bypasses the guard for its own back.

The draft stays uncontrolled per apps/mobile/AGENTS.md "Text inputs": the
loaded value reaches the field directly, the comparison baseline and the
draft live in refs, and only the derived dirty flag reaches state.

Mounted suite covers the growing field, the clean/dirty back paths, the
dialog save, the save-button bypass, and the refetch-while-editing case.
@iscekic iscekic added drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer labels Oct 17, 2026
@kilo-code-bot

kilo-code-bot Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental delta since the last review (an iOS InputAccessoryView "Done" bar for the multiline instructions field, a named SaveReviewConfigMutation alias, and the matching react-native test stubs) adds no runtime, security, or memory-leak problem; the previously reviewed draft-guard logic is unchanged.

Assumptions: [INFERENCE] the new accessory path is iOS-only, so neither the Android end-to-end run nor the mounted suite exercises it (the test stubs InputAccessoryView, Pressable, and Keyboard as host elements with no assertion) — its behavior rests on React Native's iOS implementation; verify on an iOS device when convenient. No leak vectors were added: no listeners, timers, subscriptions, or effects with uncancelled work, and the nativeID is a single module constant reused by only this screen.

Files Reviewed (2 files)
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.mounted.test.tsx
Previous Review Summary (commit 8fb7e7a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 8fb7e7a)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The two-file mobile change (a growing Custom-instructions editor plus a useSettingsBackGuard dirty-exit guard) matches the established security-agent guard pattern, keeps the draft in refs per apps/mobile/AGENTS.md, and introduces no memory leaks or logic errors in the changed lines.

Files Reviewed (2 files)
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx
  • apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.mounted.test.tsx

Reviewed by deepseek-flash-latest · Input: 62.9K · Output: 12.6K · Cached: 725K

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added merge:human Substantive functional risk requires human review. merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. and removed merge:human Substantive functional risk requires human review. labels Oct 19, 2026
@iscekic
iscekic added this pull request to the merge queue Oct 21, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 21, 2026
…nstructions-draft-guard

# Conflicts:
#	apps/mobile/src/app/(app)/(tabs)/(3_profile)/code-reviewer/[scope]/[platform]/(edit)/instructions.tsx
@iscekic iscekic removed the merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. label Oct 22, 2026
…d suite

The editor renders an iOS keyboard accessory bar and dismisses the keyboard
from it, so the mounted suite's react-native stub provides those exports.
@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Android proof from the local dev stack with an owned fake account and a seeded review configuration.

  • Reopening the Custom instructions editor: the multi-line value is shown from its first stored line, and the field grows with the content instead of clipping the start.
  • Android system back while the draft was dirty: the unsaved-changes dialog offers Save changes, Discard, and Keep editing, and the edited draft stays in the field behind it.

After Save, the stored configuration held the edited value, and back on a clean draft left the screen with no dialog.

Editor showing the stored instructions from the first line

Unsaved changes dialog with the draft intact

maphew pushed a commit to maphew/kilo-cloud that referenced this pull request Oct 30, 2026
… long drafts (Kilo-Org#9206)

* fix(mobile): bound the code reviewer instructions editor field

A stored draft of a few thousand characters made the multiline field as
tall as its whole content, so the deprecation note and Save sat outside
the viewport, the page had almost no scroll range of its own, and with
the keyboard up the field was clipped mid-text.

Cap the field at ten `leading-[1.25rem]` line boxes plus its `p-3`
padding, the same bounded-editor shape the profile, MCP, slash-command
and skill editors already use, with a cap aligned to whole lines so a
caret scroll never paints the top visible line cut. The `min-h-32` floor
from Kilo-Org#7892 still grows the field for a short value, and above the cap the
field scrolls its own text.

A capped field opens at the platform caret, which sits at the end of the
stored value, so the first line would be out of sight — the state Kilo-Org#7892
removed. Once the field and its content are measured, a value taller
than the usable field puts the caret on the first line; a value that
fits keeps the platform caret.

* fix(mobile): leave the caret alone once the instructions draft is edited

The first-line alignment belongs to the stored value's opening state.
Both platforms report a content size for the empty field at mount, so it
is normally spent before the reader touches the field; a mount that
reports none, such as a recycled view, would leave it pending and fire it
on the reader's own first paste, pulling the caret back to the start.

Record the first text change and skip the alignment after it, with a
mounted case that feeds an edit before the overflow measurement.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

drain Work item from the findings drain explorer:android Finding surfaced by the Android explorer merge:bot Bot-owned review; merge when ready once readiness passes and a review approves.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants