Skip to content

fix(mobile): keep composer attachment counts and names within bounds - #8569

Merged
iscekic merged 4 commits into
mainfrom
drain/composer-attachment-hardening
Oct 26, 2026
Merged

iscekic merged 4 commits into
mainfrom
drain/composer-attachment-hardening

Conversation

@iscekic

@iscekic iscekic commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on a local dev stack with the Android dev client, signed in locally, one picker selection larger than the composer's file limit produced only the allowed number of attachment chips, showed the composer's existing excess-selection notice, and issued an upload-slot request only for the chips that were kept. The attach control then reported disabled at the limit, re-enabled after a chip was removed, and accepted one more file with a single new upload-slot request. Separately, with a first selection still being processed, a second selection was admitted only up to the remaining capacity: the chip row finished at the limit, the composer showed its maximum-files notice, and the backend recorded upload-slot requests for the first selection only. Chip labels came from the shared filename normalization on the real surface.

Change

The mobile composer keeps its attachment count and chip names within the configured bounds: capacity is claimed before the asynchronous measurement work and re-checked against the live chip list at commit, uploads start only for chips that are kept, restored chips obey the same limit, and selected and restored names go through the shared filename normalization.

Verification

  • Stack: the repository's local dev stack on this VM, the Android emulator, and the CI-built Android dev client loading this branch's bundle from the local Metro server, signed in through the stack's local email-code flow. The backend request log provided provenance.
  • The head is unchanged by this follow-up; the served working tree was that head plus regenerated local environment files, and no source file was modified during the run.
  • Observed on device: one selection larger than the limit leaves the composer at the limit and shows the composer's existing excess-selection notice, read from the accessibility tree; the backend recorded an upload-slot request per kept chip and none for the surplus.
  • Observed on device: at the limit the attach control reports enabled="false"; after removing one chip it reports enabled="true", and one further file is accepted, restoring the limit with exactly one new upload-slot request.
  • Observed on device: chips display the normalized names produced by the shared sanitizer, including a long name that stays intact.
  • Limitation: the local stack has no object storage configured, so every upload-slot request failed and chips settled into the upload-error state. The cap, the notice, and the request accounting are independent of that failure.
  • Observed on device: two selections overlapping while the first was still processing finished at the limit, with the maximum-files notice and no upload-slot request for the second selection.
  • Because the attach control reports disabled at the limit, the same notice path is otherwise reached through the non-picker entries (a shared or restored selection); the hook's unit tests (pnpm -C apps/mobile exec vitest run src/lib/agent-attachments) cover those orderings directly, including names carrying separators and dot segments.
  • No source changed in this follow-up; the earlier format, typecheck, and unit results on this head still apply.

Production prerequisites

None.

Proxy answers

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

  1. Question: Commit model: To make the attachment cap hold under concurrent selection, how far should the hook's state bookkeeping change? commitAttachments currently writes attachmentsRef inside a React functional updater, so the ref only advances when React flushes. The clamp needs a synchronous, authoritative count.
    Answer: Ref becomes authoritative
    Confidence: 0.75 · Precedent: Autonomy: fix the root cause and the class of the issue — "fix class of issue preventing progress"
  2. Question: restoreChips: restoreChips re-adds chips after a failed optimistic send (normally into an empty list) and today appends without a cap. When the cap would be exceeded, what should it do?
    Answer: Clamp silently
    Confidence: 0.55 · Precedent: Speed: do the least work possible with no drop in quality — "do the least amount of work possible, with no drop in quality"
  3. Question: Name order: Which composition produces the stored attachment name: sanitize the picker name then apply the normalized extension, or append the extension first and sanitize the result?
    Answer: Sanitize the picker name first, apply the normalized extension, then run the sanitizer once more on the result. A bare dot segment becomes file.bin, and the stored value still passes through the sanitizer and stays within the byte budget. Add a test for ., .., and an over-long name.
    Confidence: 0.50 · Precedent: Autonomy: fix the root cause and the class of the issue — "if anything repeats the same mistake over and over again, fix the class"
  4. Question: Sanitize scope: Where should the filename sanitizer apply?
    Answer: Chip name + restored fallback
    Confidence: 0.85 · Precedent: Autonomy: fix every affected surface — "fix this for all shared services model pickers on all surfaces"
  5. Question: Gate transport: the public-text judge returned HTTP 429 for publication and completion. May I temporarily route this VM's credential lookup to an already-configured internal credential for publication, then restore the original line, leaving gate code, rules, and production settings unchanged?
    Answer: Temporary local routing
    Confidence: 0.74 · Precedent: Speed and least work: do not let spend caps or quota stop progress; Autonomy: blocked is not an acceptable end state and narrowly scoped unblocking changes are approved

Attachment additions reserve their capacity before any asynchronous work, and
the commit recomputes the remaining room from the live chip list. Uploads start
only for the chips that were kept, and a selection that does not fit gets the
existing truncated-selection feedback. App-owned cache files of dropped
candidates are released.

The attachment ref advances before React renders, so the room a commit reads
and the list it appends to are always the same list.

Chip names and the submission's original name are composed through the
filename normalizer, which keeps a single safe file segment within the byte
budget. restoreChips is bounded like every other append.
Attachment additions reserve their capacity before any asynchronous work, and
the commit recomputes the remaining room from the live chip list. Uploads start
only for the chips that were kept, and a selection that does not fit gets the
existing truncated-selection feedback. App-owned cache files of dropped
candidates are released.

The attachment ref advances before React renders, so the room a commit reads
and the list it appends to are always the same list.

Chip names and the submission's original name are composed through the
filename normalizer, which keeps a single safe file segment within the byte
budget. restoreChips is bounded like every other append.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening janitor:features-workflows Janitor finding family: features-workflows labels Oct 22, 2026
@iscekic

iscekic commented Oct 22, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Android runtime captures from the local dev stack (fake account, non-production data). The composer was given six documents in one picker selection.

The first capture shows the strip and the truncated-selection message the composer produced. The second shows the end of the strip, where the remaining kept chips sit. Across the strip, uiautomator enumerated exactly five chips, and the app made exactly five upload admissions for that selection.

Chips show a retryable upload state afterwards because this stack mocks the object-storage credentials; the PUT leg cannot succeed locally. Admission, chip count, and the message are what these captures show.

Composer strip after a six-document selection, with the kept-count message

End of the same strip, showing the remaining kept chips

@kilo-code-bot

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

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change moves the full-composer toast into a shared announceComposerFull helper and calls it when a claimed selection commits with zero room, closing the previously reported silent-drop gap; the new test covers the filled-during-measurement path and the change is otherwise behavior-preserving.

Assumptions: [INFERENCE] Review is limited to the incremental diff f21003ce..10fd9ed5 (two files); no test execution was possible in this read-only session, so the new test's assertions were checked by reading the mocked toast/fileDelete/Paths.cache setup rather than by running vitest.

Files Reviewed (2 files)
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts - 0 issues (previous silent-drop finding resolved: announceComposerFull runs before the early return, and dropped candidates still release their cache files)
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts - 0 issues
Previous Review Summary (commit f21003c)

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

Previous review (commit f21003c)

Status: 1 Issue Found | Recommendation: Address before merge

Assumptions: [INFERENCE] The new dropped-candidate cleanup deletes cache files by URI; whether expo-document-picker can return a cache URI that a still-live chip already references (same file re-selected) could not be verified from this checkout, so that deletion path is unproven rather than flagged.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts 568 A selection whose kept list is empty is dropped silently: its cache files are deleted and the function returns before the onlyAddingFiles warning, so the user gets no feedback. selection.capacityTruncated is already known; surface the warning/error on this path.
Files Reviewed (2 files)
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts - 1 issue
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 30.8K · Output: 3.7K · Cached: 284.8K

Review guidance: REVIEW.md from base branch main

When the strip reaches its limit while an add is still measuring, the add now
announces the full-composer message instead of returning without feedback. The
message lives in one helper used by both capacity paths.
@iscekic iscekic added the merge:bot Bot-owned review; merge when ready once readiness passes and a review approves. label Oct 22, 2026
@iscekic
iscekic enabled auto-merge October 22, 2026 11:29
@iscekic
iscekic disabled auto-merge October 22, 2026 19:26
@iscekic iscekic added merge:blocked:insufficient-e2e Verification does not cover the change intent:ok Janitor change confirmed as a real bug fix, not intended behavior labels Oct 22, 2026
@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Local Android dev-client run of the composer attachment flow on this branch. Captures show the chip row at the configured limit, the composer's status notice, the attach control reporting disabled at the limit, and one more file accepted after a chip was removed. Uploads settle into the local upload-error state because the local stack has no object storage, which does not affect the chip count or the upload-slot request accounting.

Composer chip row at the configured limit

Composer status notice

Composer chip row after removing a chip

@iscekic

iscekic commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

bot: Local Android run of the composer. The capture shows the attachment row at the configured limit together with the composer's maximum-files notice, and the backend recorded upload-slot requests only for the chips that were kept.

Composer attachment row at the configured limit

@iscekic iscekic removed the merge:blocked:insufficient-e2e Verification does not cover the change label Oct 24, 2026
@iscekic
iscekic enabled auto-merge October 24, 2026 16:33

@eshurakov eshurakov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Shallow review: no critical issues found.

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 intent:ok Janitor change confirmed as a real bug fix, not intended behavior janitor:features-workflows Janitor finding family: features-workflows janitor:trust Janitor finding: trust boundary hardening 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