From a161d3f9005c22b0b50fe87ecf9d699591403228 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 27 Oct 2026 14:23:47 +0000 Subject: [PATCH] Put the reply bar's expand button inside the field on iOS On iOS the reply field is a Liquid Glass plate, and a field's own controls belong inside it at the trailing edge, so the expand button now sits there and stays at the bottom as the draft grows. It's beside the field's press target rather than in it, so pressing it doesn't fold the earlier commits away as a tap on the field does. Android keeps it ahead of the field. Co-Authored-By: Claude Opus 5.5 --- .../views/native/commit-detail.blade.php | 46 ++++++++++++------- tests/Feature/Screens/CommitDetailTest.php | 16 ++++++- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/resources/views/native/commit-detail.blade.php b/resources/views/native/commit-detail.blade.php index abcf20a3..06f75c8b 100644 --- a/resources/views/native/commit-detail.blade.php +++ b/resources/views/native/commit-detail.blade.php @@ -113,26 +113,40 @@ the surface and the hairline above it belong to this column, or the replies show through. On iOS the bar is bare and the field is a Liquid Glass plate instead, the way the HIG floats controls over content; the hairline stays as a clear 1pt so the bar keeps the height Display::ReplyBarHeight counts on. + + The expand button opens the reply in the composer sheet, which has the photos and preview the bar has no + room for. On iOS it sits inside the plate at its trailing edge, where iOS puts a field's own controls, and + stays at the bottom as the field grows. On Android it leads the bar, ahead of the field. --}} + @php($expandInField = \Native\Mobile\Platform::isIos()) - - {{-- The bar has no room for photos or a preview, so this opens the reply in the composer sheet, which has them. --}} - @include('native.partials.composer-tool', ['ref' => 'reply-expand', 'icon' => \App\Icons\Lucide::Maximize2, 'label' => 'Reply with photos or a preview', 'press' => "replyInSheet('{$commit->id}')"]) - {{-- EDGE sends no focus event, so a press on the field is how the page learns you're replying. --}} - - - + + @unless ($expandInField) + @include('native.partials.composer-tool', ['ref' => 'reply-expand', 'icon' => \App\Icons\Lucide::Maximize2, 'label' => 'Reply with photos or a preview', 'press' => "replyInSheet('{$commit->id}')"]) + @endunless + + {{-- + EDGE sends no focus event, so a press on the field is how the page learns you're replying. + The expand button is beside it rather than in it, so pressing it doesn't count. + --}} + + + + @if ($expandInField) + @include('native.partials.composer-tool', ['ref' => 'reply-expand', 'icon' => \App\Icons\Lucide::Maximize2, 'label' => 'Reply with photos or a preview', 'press' => "replyInSheet('{$commit->id}')"]) + @endif + @include('native.partials.body-counter', ['body' => $reply, 'ref' => 'reply-budget']) Reply diff --git a/tests/Feature/Screens/CommitDetailTest.php b/tests/Feature/Screens/CommitDetailTest.php index 7874676c..aa648047 100644 --- a/tests/Feature/Screens/CommitDetailTest.php +++ b/tests/Feature/Screens/CommitDetailTest.php @@ -104,6 +104,18 @@ function fakeThread(array $commit = [], array $replies = [], array $extra = []): ->and(idsDownTo($screen->tree(), 'reply-input'))->toBe($field); }); +it('puts the expand button inside the reply field on iOS, and ahead of it on Android', function (string $platform, array $row, array $plate) { + fakeThread(); + + $screen = Native::visit('/log/commits/c2', platform: $platform); + + expect(childRefs($screen, 'reply-row'))->toBe($row) + ->and(childRefs($screen, 'reply-plate'))->toBe($plate); +})->with([ + 'iOS' => ['ios', ['reply-plate', 'send-reply'], ['reply-field', 'reply-expand']], + 'Android' => ['android', ['reply-expand', 'reply-plate', 'send-reply'], ['reply-field']], +]); + it('runs the rail from the folded ancestors into the parent, which shows its avatar', function () { $ada = userJson(['id' => 'u2', 'handle' => 'ada', 'name' => 'Ada', 'lane' => 1]); fakeApi([ @@ -718,7 +730,7 @@ function fakeThread(array $commit = [], array $replies = [], array $extra = []): ->and($bar['children'][0]['style']['bg_color'] ?? null)->toBe(config('native-ui.theme.light.surface')) ->and($bar['children'][0]['props']['dark_bg_color'] ?? null)->toBe(config('native-ui.theme.dark.surface')) ->and($bar['children'][0]['children'][0]['style']['bg_color'] ?? null)->toBe(config('native-ui.theme.light.hairline')) - ->and(nodeByRef($screen, 'reply-field')['props']['glass'] ?? null)->toBeNull(); + ->and(nodeByRef($screen, 'reply-plate')['props']['glass'] ?? null)->toBeNull(); }); it('leaves the reply bar bare on iOS and floats the field on Liquid Glass', function () { @@ -730,7 +742,7 @@ function fakeThread(array $commit = [], array $replies = [], array $extra = []): expect($bar['children'][0]['ref'] ?? null)->toBe('reply-bar') ->and($bar['children'][0]['style']['bg_color'] ?? null)->toBeNull() ->and($bar['children'][0]['children'][0]['style']['bg_color'] ?? null)->toBeNull() - ->and(nodeByRef($screen, 'reply-field')['props']['glass'] ?? null)->toBe(1); + ->and(nodeByRef($screen, 'reply-plate')['props']['glass'] ?? null)->toBe(1); }); it('ends the page above the reply bar, and further as the draft grows', function () {