Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
252109b
feat(mobile-a11y): reflow tab shell + header at large font scale
iscekic Jul 23, 2026
6658e32
fix(mobile-a11y): use AA-contrast token for muted-soft text
iscekic Jul 23, 2026
c2de0c4
feat(mobile-a11y): expose Kilo Chat message content + actions to scre…
iscekic Jul 23, 2026
bcbf161
feat(mobile-a11y): reflow core/profile/auth layouts at max text
iscekic Jul 23, 2026
01268bb
feat(mobile-a11y): scale PR diff text with bounded font scale
iscekic Jul 23, 2026
ed0f5a3
feat(mobile-a11y): announce and focus blocking agent cards
iscekic Jul 23, 2026
2304cc9
feat(mobile-a11y): align Agent message bubble a11y with chat contract
iscekic Jul 23, 2026
d8de3dd
feat(mobile-a11y): non-color diff markers + 44pt selection
iscekic Jul 23, 2026
83c6de0
fix(mobile-a11y): safe-area scroll for blocking recovery screens
iscekic Jul 23, 2026
2f2868b
fix(mobile-a11y): keep Android login submit reachable above the IME
iscekic Jul 23, 2026
b4093f0
docs(mobile-a11y): document portrait orientation essential exception
iscekic Jul 23, 2026
07be190
feat(mobile-a11y): announce consequential toasts to screen readers
iscekic Jul 23, 2026
f7b5e02
fix(mobile-a11y): keep OTP actions reachable at max text with keyboard
iscekic Jul 23, 2026
924f148
fix(mobile-a11y): label permission Retry button for screen readers
iscekic Jul 23, 2026
a275f36
Merge remote-tracking branch 'origin/main' into feature/mobile-audit-…
iscekic Jul 27, 2026
94b06ab
fix(mobile-e2e): handle SpringBoard "Open in Kilo?" confirmation afte…
iscekic Jul 27, 2026
e10414f
docs(mobile): record e2e environment learnings for the W1-B main merge
iscekic Jul 27, 2026
948afd5
docs(mobile): record hermetic stub files-endpoint gap
iscekic Jul 27, 2026
7e0760b
Merge remote-tracking branch 'origin/main' into feature/mobile-audit-…
iscekic Jul 27, 2026
52755d1
Merge origin/main into feature/mobile-audit-w1-accessibility-foundation
iscekic Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions apps/mobile/.kilo/WORKFLOW_LEARNINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,27 @@ Then wait event-driven with an `until grep -q EXITCODE= "$LOG"` loop that also b
**Symptom.** The completion gate wants "Kilobot has reviewed the latest head", but the review no longer arrives as inline threads: with the bot skip/permit config (#4765) on main, a clean review produces a green `Kilo Code Review` check plus exactly one issue comment from `kilo-code-bot[bot]` headed `Status: No Issues Found | Recommendation: Merge`.

**Fix.** That combination — green check on the current head, the no-issues summary comment, zero review threads (`gh api repos/.../pulls/<n>/comments` empty) — *is* the reviewed-with-no-findings state. There is nothing to reply to or resolve; the gate is met. A `BLOCKED`/`REVIEW_REQUIRED` merge state at that point only means the requested human review is pending.

### SpringBoard `Open in "Kilo"?` confirmation blocks `simctl openurl` (2026-07-27, PR #4697 main-merge)

- Symptom: `e2e/login.sh` fails at settle-app: after preflight's `xcrun simctl openurl` a SpringBoard dialog `Open in "Kilo"?` (curly quotes, Cancel/Open buttons) stays on screen; `settle-app.yaml` only matched the Safari wording `Open this page in "Kilo"?` and timed out.
- Cause: origin/main added `associatedDomains: ['applinks:app.kilo.ai']` (universal links) in `app.config.ts`; with the merged build installed, opening the custom scheme via `simctl openurl` surfaces a SpringBoard confirmation the flows did not handle.
- Fix: match both wordings in `e2e/flows/settle-app.yaml` and `e2e/flows/open-app.yaml` (`Open in ["“”]Kilo["“”]\?` alongside the Safari string) and tap `Open` in the same bounded optional-prompt slot; updated the stale "skips Safari's confirmation" bullet in `e2e/AGENTS.md`.

### Maestro `IOSDriverTimeoutException` under multi-simulator load (2026-07-27, PR #4697 verifier rerun)

- Symptom: every Maestro command against a claimed iOS simulator fails with `xcuitest.installer.LocalXCTestInstaller$IOSDriverTimeoutException: iOS driver not ready in time`, even with `MAESTRO_DRIVER_STARTUP_TIMEOUT=300000`; `simctl openurl` may also time out (`NSPOSIXErrorDomain code=60`).
- Cause: a stale `xcodebuild test-without-building` process left bound to the UDID after a killed Maestro run (check `ps aux | grep xcodebuild` and match the `-xctestrun` temp path / `id=<udid>`), compounded by several same-type simulators booted by sibling worktrees.
- Fix: kill only the `xcodebuild` process whose xctestrun path contains your UDID, then `xcrun simctl shutdown <udid> && xcrun simctl boot <udid>` (app and login state survive; `login.sh` is idempotent). Validate with a one-step `takeScreenshot` flow before dispatching the verifier again.

### "GitHub connection expired" against hermetic stub = git-token-service 503 (2026-07-27, PR #4697 C3)

- Symptom: PR-review E2E with the hermetic GitHub stub opens the PR then stalls on "GitHub connection expired / Check connection"; the stub request log shows only the first `pulls/<n>` fetch and no further traffic; nextjs logs repeated `githubPrReview.getPullRequest 412`.
- Cause: `withGitHubUserTokenRetry` resolves the user's GitHub token through git-token-service before any outbound call; the service's `POST /internal/github-user-authorizations/token` returned `503 authentication_unavailable` because its `NEXTAUTH_SECRET_DEV` Secrets Store binding had never been created in this worktree (Secrets Store state is local to each Worker directory). A missing-URL or key-drift `USER_GITHUB_APP_TOKEN_*` envelope produces the same 412 surface.
- Fix: `pnpm dev:env -y cloudflare-git-token-service` (creates `NEXTAUTH_SECRET_DEV` and syncs the `USER_GITHUB_APP_TOKEN_*` envelope into the worker env from root `.env.local`), then `pnpm dev:restart cloudflare-git-token-service`. Smoke with an authenticated `GET /api/trpc/githubPrReview.getPullRequest?...owner=kilo-stub...` — expect 200, not 412. Note nextjs reads `GIT_TOKEN_SERVICE_API_URL` from `apps/web/.env.development.local` (`@url cloudflare-git-token-service`), not from root `.env.local`.

### Hermetic GitHub stub lacks `GET /pulls/{n}/files` — Files-tab E2E 404s (2026-07-27, PR #4697 C3)

- Symptom: PR-review Files tab shows "Pull request unavailable" while Overview loads fine; nextjs logs `githubPrReview.listFiles 404`; stub request log shows every pinned endpoint hit except `/files`.
- Cause: the stub's pinned surface (REST pull/repo/check-runs/statuses + GraphQL review ops) does not cover `GET /repos/{owner}/{repo}/pulls/{n}/files`, which `listFiles` needs.
- Workaround (one-off): temporarily add a `/files` handler returning patched-file fixtures, restart the stub tmux session, verify, then restore `server.mjs` byte-identical. Permanent fix (teach the stub `/files` + pagination) is intentionally left out of scope; do it as a dedicated harness change when a run needs Files-tab E2E regularly.
5 changes: 5 additions & 0 deletions apps/mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const config: ExpoConfig = {
owner: 'kilocode',
slug: 'kilo-app',
version: '1.0.3',
// Portrait-only is an accepted, documented product deviation from WCAG 1.3.4
// (Orientation). Landscape layouts and iPad split-view/multitasking are out
// of scope; `ios.requireFullScreen` below enforces that. This is not claimed
// as a WCAG "essential" exception, which requires functionality to
// fundamentally change with orientation.
orientation: 'portrait',
icon: './assets/images/logo.png',
scheme: 'kiloapp',
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/e2e/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ xcrun simctl openurl <udid> \
"exp+kilo-app://expo-development-client/?url=http%3A%2F%2F<lan-ip>%3A<metro-port>"
```

- Prefer `simctl openurl` for scheme reconnection; it skips Safari's external-app confirmation. When a flow intentionally goes through Safari or a WebView, look for the exact message `Open this page in "Kilo"?` and tap the exact `Open` accessibility action — one bounded optional prompt inside the existing five-second optional-prompt budget, never a new fixed wait.
- Prefer `simctl openurl` for scheme reconnection; it skips Safari's external-app confirmation. Since universal links (`associatedDomains`) were configured, iOS may instead show a SpringBoard confirmation with the exact message `Open in "Kilo"?` (curly or straight quotes) — the shared launch flows match both wordings and tap `Open`. When a flow intentionally goes through Safari or a WebView, look for the exact message `Open this page in "Kilo"?` and tap the exact `Open` accessibility action — one bounded optional prompt inside the existing five-second optional-prompt budget, never a new fixed wait.
- Before testing, capture the `mobile` pane and verify `Starting project at <this-worktree>/apps/mobile` plus a fresh `iOS Bundled` line. Seeing the Kilo login screen does not prove the bundle came from this worktree.
- The dev client reads `expoConfig.extra.apiBaseUrl` and `_internal.projectRoot` from Metro's manifest; the login preflight checks both against this worktree. After env changes: regenerate env, restart Metro, reconnect the dev client to the exact Metro URL, and reload. Rebuild only when native config or plugins changed.
- The shared launch flows dismiss the clean-install tracking alert, accept the Expo dev-menu introduction with `Continue`, and close the full developer menu (Fast Refresh / Element Inspector) with its `Close` accessibility action.
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/e2e/flows/open-app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ appId: com.kilocode.kiloapp
text: 'Kilo'
# Cold launch and bundling are slow; wait for any known state before settling.
- extendedWaitUntil:
visible: 'Open this page in "Kilo"\?|Allow “Kilo” to track your activity across other companies’ apps and websites\?|Ask App Not to Track|This is the developer menu.*|Fast Refresh|Element Inspector|“Kilo” Would Like to Send You Notifications|HOME|Home, tab, 1 of 4|Welcome to Kilo Code|Accept and continue'
visible: 'Open this page in "Kilo"\?|Open in ["“”]Kilo["“”]\?|Allow “Kilo” to track your activity across other companies’ apps and websites\?|Ask App Not to Track|This is the developer menu.*|Fast Refresh|Element Inspector|“Kilo” Would Like to Send You Notifications|HOME|Home, tab, 1 of 4|Welcome to Kilo Code|Accept and continue'
timeout: 30000
- runFlow: settle-app.yaml
16 changes: 9 additions & 7 deletions apps/mobile/e2e/flows/settle-app.yaml
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# Settles an already-running app: handles the Safari external-app prompt,
# tracking prompt, Expo developer-menu introduction and menu, and notification
# permission, ending on Home, the login page, or the consent gate. Never
# restarts the app; open-app.yaml is the cold-launch wrapper around this flow.
# Settles an already-running app: handles the Safari external-app prompt and
# the SpringBoard custom-scheme confirmation (`Open in "Kilo"?`, shown by
# `simctl openurl` since universal links were configured), the tracking prompt,
# Expo developer-menu introduction and menu, and notification permission,
# ending on Home, the login page, or the consent gate. Never restarts the app;
# open-app.yaml is the cold-launch wrapper around this flow.
appId: com.kilocode.kiloapp
---
- extendedWaitUntil:
visible: 'Open this page in "Kilo"\?|Allow “Kilo” to track your activity across other companies’ apps and websites\?|Ask App Not to Track|This is the developer menu.*|Fast Refresh|Element Inspector|“Kilo” Would Like to Send You Notifications|HOME|Home, tab, 1 of 4|Welcome to Kilo Code|Accept and continue'
visible: 'Open this page in "Kilo"\?|Open in ["“”]Kilo["“”]\?|Allow “Kilo” to track your activity across other companies’ apps and websites\?|Ask App Not to Track|This is the developer menu.*|Fast Refresh|Element Inspector|“Kilo” Would Like to Send You Notifications|HOME|Home, tab, 1 of 4|Welcome to Kilo Code|Accept and continue'
timeout: 15000
- extendedWaitUntil:
visible: 'Open this page in "Kilo"\?|Ask App Not to Track|This is the developer menu.*|Fast Refresh|Element Inspector|“Kilo” Would Like to Send You Notifications'
visible: 'Open this page in "Kilo"\?|Open in ["“”]Kilo["“”]\?|Ask App Not to Track|This is the developer menu.*|Fast Refresh|Element Inspector|“Kilo” Would Like to Send You Notifications'
timeout: 3000
optional: true
- runFlow:
when:
visible: 'Open this page in "Kilo"\?'
visible: 'Open this page in "Kilo"\?|Open in ["“”]Kilo["“”]\?'
commands:
- tapOn: 'Open'
- extendedWaitUntil:
Expand Down
8 changes: 6 additions & 2 deletions apps/mobile/src/app/(app)/(tabs)/(1_kiloclaw)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useManualRefresh } from '@/lib/hooks/use-manual-refresh';
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
import { useUnreadCounts } from '@/lib/hooks/use-unread-counts';
import { chatSandboxPath } from '@/lib/kilo-chat-routes';
import { getTabBarOverlayHeight } from '@/lib/tab-bar-layout';
import { getEffectiveTabBarHeight } from '@/lib/tab-bar-layout';

export default function KiloClawTab() {
const router = useRouter();
Expand All @@ -42,7 +42,11 @@ export default function KiloClawTab() {

const showInstanceSkeleton = entryDecision.kind === 'loading' || onboardingQuery.isPending;
const emptyStateContainerStyle = {
paddingBottom: getTabBarOverlayHeight(bottom, Platform.OS, fontScale),
paddingBottom: getEffectiveTabBarHeight({
bottomInset: bottom,
platform: Platform.OS,
fontScale,
}),
};

const [manualRefreshing, handleRefresh] = useManualRefresh(
Expand Down
21 changes: 15 additions & 6 deletions apps/mobile/src/app/(app)/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { BlurBar } from '@/components/ui/blur-bar';
import { Text } from '@/components/ui/text';
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
import {
getTabBarOverlayHeight,
getEffectiveTabBarHeight,
getTabBarIconSize,
shouldHideTabBar,
shouldShowTabLabel,
TAB_LABEL_WRAP_FONT_SCALE,
} from '@/lib/tab-bar-layout';

Expand Down Expand Up @@ -52,7 +54,13 @@ export default function TabsLayout() {
const { bottom } = useSafeAreaInsets();
const { fontScale } = useWindowDimensions();
const hideTabs = shouldHideTabBar(pathname);
const tabBarHeight = getTabBarOverlayHeight(bottom, Platform.OS, fontScale);
const showTabLabel = shouldShowTabLabel(fontScale);
const tabBarHeight = getEffectiveTabBarHeight({
bottomInset: bottom,
platform: Platform.OS,
fontScale,
});
const tabIconSize = getTabBarIconSize(fontScale);

return (
<Tabs
Expand All @@ -73,6 +81,7 @@ export default function TabsLayout() {
height: tabBarHeight,
position: 'absolute',
},
tabBarShowLabel: showTabLabel,
}}
>
<Tabs.Screen
Expand All @@ -82,7 +91,7 @@ export default function TabsLayout() {
tabBarAccessibilityLabel: 'Home, tab, 1 of 4',
tabBarLabel: ({ focused }) => <TabLabel label="Home" focused={focused} />,
tabBarIcon: ({ color, focused }) => (
<House size={22} color={color} strokeWidth={focused ? 2 : 1.5} />
<House size={tabIconSize} color={color} strokeWidth={focused ? 2 : 1.5} />
),
}}
listeners={{
Expand All @@ -103,7 +112,7 @@ export default function TabsLayout() {
/>
),
tabBarIcon: ({ color, focused }) => (
<MessageSquare size={22} color={color} strokeWidth={focused ? 2 : 1.5} />
<MessageSquare size={tabIconSize} color={color} strokeWidth={focused ? 2 : 1.5} />
),
}}
listeners={{
Expand All @@ -121,7 +130,7 @@ export default function TabsLayout() {
tabBarAccessibilityLabel: 'Agents, tab, 3 of 4',
tabBarLabel: ({ focused }) => <TabLabel label="Agents" focused={focused} />,
tabBarIcon: ({ color, focused }) => (
<Bot size={22} color={color} strokeWidth={focused ? 2 : 1.5} />
<Bot size={tabIconSize} color={color} strokeWidth={focused ? 2 : 1.5} />
),
}}
listeners={{
Expand All @@ -137,7 +146,7 @@ export default function TabsLayout() {
tabBarAccessibilityLabel: 'Profile, tab, 4 of 4',
tabBarLabel: ({ focused }) => <TabLabel label="Profile" focused={focused} />,
tabBarIcon: ({ color, focused }) => (
<UserRound size={22} color={color} strokeWidth={focused ? 2 : 1.5} />
<UserRound size={tabIconSize} color={color} strokeWidth={focused ? 2 : 1.5} />
),
}}
listeners={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type ActiveRequest = { requestId: string } | null | undefined;

type BlockingInteraction = 'question' | 'permission' | 'none';
export type BlockingInteraction = 'question' | 'permission' | 'none';

export function getBlockingInteraction(input: {
activeQuestion: ActiveRequest;
Expand Down
Loading
Loading