Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7f8ad26
Centre a readable column and pair onboarding on expanded windows
ampagent Nov 4, 2026
f3f27f2
Patch the mobile-ui pager to page by its own width beside a side bar
ampagent Nov 5, 2026
7324894
Restore horizontal pager selection after warm resume
ampagent Nov 5, 2026
c216337
Retain horizontal pager selection across active fold resizing
ampagent Nov 5, 2026
64ebdfe
Keep live feed and conversation panes across Duo resizing
ampagent Nov 5, 2026
62858eb
Merge upstream main into the Duo contribution
ampagent Nov 5, 2026
854f3b1
Fix Duo runtime layout after display transitions
ampagent Nov 6, 2026
6eddad7
Extend Duo feed content beneath bottom safe area
ampagent Nov 7, 2026
9594f0d
Merge commit '1c82319' into duo-adaptive-layouts
cmcmanus96 Nov 7, 2026
153365d
Preserve Duo navigation chrome and composer clearance
cmcmanus96 Nov 7, 2026
07c5580
Enable iPhone landscape and fill the profile viewport
cmcmanus96 Nov 7, 2026
f2ff64e
Keep nested navigation controls tappable after Duo rotation
cmcmanus96 Nov 7, 2026
6c3a75b
Encapsulate retained panes and cover photo event ownership
cmcmanus96 Nov 7, 2026
f2e38d0
Resume retained panes after native back and close
cmcmanus96 Nov 7, 2026
56a57fa
Align retained navigation actions with the pane safe area
cmcmanus96 Nov 7, 2026
1188ded
Deliver retained pane state during sheet dismissal
cmcmanus96 Nov 7, 2026
ea3f91a
Keep search separators and notification filters inside the rail
cmcmanus96 Nov 7, 2026
b143b20
Use a consistent themed surface for iOS navigation headers
cmcmanus96 Nov 7, 2026
b32201b
Keep native separators within their content bounds
cmcmanus96 Nov 7, 2026
9b8cdbf
Announce an author's avatar by name, not as an unfinished template
cmcmanus96 Nov 8, 2026
765073d
Point Remotes' empty state at the pencil button, not a label nobody sees
cmcmanus96 Nov 8, 2026
2b89bc5
Reflow the profile into two columns on the unfolded inner display
cmcmanus96 Nov 8, 2026
8e9836a
Keep a sheet bar button's icon out of VoiceOver and call a muted one …
cmcmanus96 Nov 8, 2026
8c1c103
Keep the onboarding lockup inside its half of the inner display, with…
cmcmanus96 Nov 8, 2026
7e03d00
Line up the Notifications and Remotes tabs with their readable column
cmcmanus96 Nov 8, 2026
fe21cb5
Line a pushed commit's reply bar up with its readable column
cmcmanus96 Nov 8, 2026
0e23aab
Make the Duo feed workspace fold-aware and follow the system bars
cmcmanus96 Nov 8, 2026
b2b7fbc
Scope the onboarding lockup's window width to iOS, centre the reply b…
cmcmanus96 Nov 8, 2026
0403f43
Lay out the Duo workspace when the fold gap moves
cmcmanus96 Nov 8, 2026
c009357
Have the pager put the profile header beside its pages once expanded
cmcmanus96 Nov 8, 2026
17a4d68
Wrap the reply bar's readable-column comment and indent its closing tag
cmcmanus96 Nov 8, 2026
7f38a2f
Keep the pager whole when its header moves beside the pages
cmcmanus96 Nov 8, 2026
2b824aa
Keep each profile list's place when the header moves beside it
cmcmanus96 Nov 8, 2026
42298e5
Merge branch 't3/native-accessibility-layout-fixes' into duo-integration
cmcmanus96 Nov 8, 2026
bcf5923
Merge branch 't3/profile-inner-two-column-reflow' into duo-integration
cmcmanus96 Nov 8, 2026
f96c26c
Put scroll indicators at the screen edge beside the Duo's vertical bar
cmcmanus96 Nov 8, 2026
aec97fd
Speak in git's voice in the empty detail pane
cmcmanus96 Nov 8, 2026
90cac43
Name the New remote button as VoiceOver announces it in the empty Rem…
cmcmanus96 Nov 8, 2026
da59a70
Derive expanded widths in tests from the breakpoint and the readable …
cmcmanus96 Nov 8, 2026
64e41ec
Say why, not the sums, in the lockup, selected row and sheet button c…
cmcmanus96 Nov 8, 2026
9c187bc
Drop the workspace background's redundant safe-area escape
cmcmanus96 Nov 8, 2026
a829290
Split evenly the same way in the workspace and the pager without a fold
cmcmanus96 Nov 8, 2026
75e1769
Re-check the fold gap on the run loop after each hinge update
cmcmanus96 Nov 8, 2026
a610a5d
Describe the scroll-edge patch by the safe area it reacts to, and its…
cmcmanus96 Nov 8, 2026
23ce67b
Merge upstream main (#170) into duo-adaptive-layouts
cmcmanus96 Nov 8, 2026
0d03a04
Keep the Duo's inner display to one column in portrait
cmcmanus96 Nov 8, 2026
f83ea45
Measure each column's side insets from the live safe area, not its la…
cmcmanus96 Nov 8, 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
9 changes: 9 additions & 0 deletions app/Devnull/Display.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ final class Display
*/
public const int ElbowLevel = 22;

/**
* Classes for a screen's scrolling column: as wide as the screen on a phone, but a
* comfortable 672pt line once the window reaches the expanded width (840pt, the inner
* display of a foldable held sideways), centred by its parent (give the parent items-center).
* A fixed width rather than max-w: EDGE stretches a w-full child across its parent and
* ignores items-center for it, and has no mx-auto.
*/
public const string ReadableColumn = 'expanded:w-[672px]';

/**
* How long ago a commit was made, as the web prints it beside the commit:
* "now", "5m", "3h", "2d", then the date, in the same time zone as dayLabel().
Expand Down
1 change: 1 addition & 0 deletions app/Devnull/RowInputs.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static function for(Commit $commit, array $scope): array
'tombstoneReplies' => array_key_exists($commit->id, $scope['tombstoneReplies'] ?? []),
'shown' => [self::changing($commit), $commit->quotedCommit === null ? null : self::changing($commit->quotedCommit)],
'poll' => [$scope['pollChoices'][$commit->id] ?? null, in_array($commit->id, $scope['voting'] ?? [], true)],
'selected' => ($scope['selectedCommit'] ?? null) === $commit->id,
];
}

Expand Down
30 changes: 29 additions & 1 deletion app/NativeComponents/CommitDetail.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ class CommitDetail extends NativeComponent

public string $id = '';

public bool $embedded = false;

/** @var array<string, mixed> */
public array $conversationData = [];

public ?Commit $commit = null;

/** @var list<Commit> */
Expand Down Expand Up @@ -169,10 +174,28 @@ class CommitDetail extends NativeComponent
*/
protected bool $forceFullFrames = true;

public function data(string $key, mixed $default = null): mixed
{
return $this->embedded ? ($this->conversationData[$key] ?? $default) : parent::data($key, $default);
}

/** @param array<string, mixed> $data */
public function applyConversationIntent(array $data): void
{
$this->conversationData = $data;
if (($data['reply'] ?? false) === true) {
$this->focusReply = true;
}
if (($data['replies'] ?? false) === true) {
$this->repliesShown = true;
}
$this->editFromData();
}

public function mount(): void
{
$this->focusReply = $this->data('reply', false) === true;
$this->repliesShown = $this->data('replies', false) === true;
$this->repliesShown = $this->embedded || $this->data('replies', false) === true;
$draft = app(Stash::class)->get($this->me()?->id, $this->replyStashName());
$this->reply = $draft->body;
$this->replyImages = $draft->images;
Expand Down Expand Up @@ -434,6 +457,11 @@ protected function showCommitTurnedDown(PendingWrite $write): void
$this->keepStagedReplies();
}

protected function opensComposer(): bool
{
return $this->embedded;
}

/**
* A reply to this commit from the bar goes back into the bar, marked as
* restored, when the bar's free. One from the sheet opens the sheet again,
Expand Down
22 changes: 21 additions & 1 deletion app/NativeComponents/Concerns/AttachesPhotos.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use App\Devnull\Data\DraftImage;
use App\Devnull\DraftImages;
use App\Devnull\Exceptions\InvalidDraftImage;
use App\NativeComponents\FeedWorkspace;
use Native\Mobile\Attributes\On;
use Native\Mobile\Events\Camera\PermissionDenied;
use Native\Mobile\Events\Camera\PhotoTaken;
Expand Down Expand Up @@ -59,20 +60,30 @@ abstract protected function photoFailed(?string $error): void;
public function pickPhotos(): void
{
if ($this->hasRoomForPhotos()) {
if ($this->nativeParentComponent instanceof FeedWorkspace) {
$this->nativeParentComponent->claimPhotoPicker($this);
}
Camera::pickImages('images', true, DraftImages::MaxImages - count($this->attachedPhotos()))->start();
}
}

public function takePhoto(): void
{
if ($this->hasRoomForPhotos()) {
if ($this->nativeParentComponent instanceof FeedWorkspace) {
$this->nativeParentComponent->claimPhotoPicker($this);
}
Camera::getPhoto()->start();
}
}

#[On(PhotoTaken::class)]
public function photoTaken(string $path = ''): void
{
if (! $this->ownsPhotoPicker()) {
return;
}

$this->attachPhotos([$path]);
}

Expand All @@ -82,17 +93,26 @@ public function photoTaken(string $path = ''): void
#[On(MediaSelected::class)]
public function photosPicked(array $files = []): void
{
if (! $this->ownsPhotoPicker()) {
return;
}

$this->attachPhotos(array_map(fn (mixed $file): string => is_array($file) && is_string($file['path'] ?? null) ? $file['path'] : '', $files));
}

#[On(PermissionDenied::class)]
public function cameraDenied(): void
{
if ($this->takesPhotos()) {
if ($this->ownsPhotoPicker() && $this->takesPhotos()) {
$this->photoFailed(DraftImages::CameraDenied);
}
}

private function ownsPhotoPicker(): bool
{
return ! $this->nativeParentComponent instanceof FeedWorkspace || $this->nativeParentComponent->ownsPhotoPicker($this);
}

/**
* The ✕ on a photo: it comes off the draft, and off the phone.
*/
Expand Down
9 changes: 7 additions & 2 deletions app/NativeComponents/Concerns/OpensCommitSheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use App\Devnull\Timezone;
use App\Devnull\Toasts;
use App\Models\PendingWrite;
use App\NativeComponents\FeedWorkspace;
use Carbon\CarbonImmutable;
use Closure;
use Native\Mobile\Facades\System;
Expand Down Expand Up @@ -344,7 +345,7 @@ public function commitFromSheet(): void
app(Stash::class)->put($me->id, $stashName, new StashedDraft);
$this->sheetImages = [];
$this->closeCommitSheet();
$this->showCommitted($write->stagedCommit($me));
$this->applyStagedCommit($write->stagedCommit($me));
$this->sendFromOutbox($write);
}

Expand Down Expand Up @@ -391,7 +392,11 @@ public function commitSheetClosed(): void
$write = $held === null ? null : app(Outbox::class)->find($held);

if ($write !== null && $write->isCommit() && $write->isFailed()) {
$this->openFailedCommit($write);
if ($this->nativeParentComponent instanceof FeedWorkspace) {
$this->nativeParentComponent->recoverOutboxDraft($write);
} else {
$this->openFailedCommit($write);
}

return;
}
Expand Down
8 changes: 8 additions & 0 deletions app/NativeComponents/Concerns/OpensInTab.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace App\NativeComponents\Concerns;

use App\NativeComponents\FeedWorkspace;
use App\NativeComponents\Layouts\TabsLayout;
use App\NativeComponents\Log;

/**
* Opens commits and profiles inside the tab they were tapped in, and never
Expand All @@ -26,6 +28,12 @@ trait OpensInTab
*/
protected function openInTab(string $path, array $data = []): void
{
if ($this->nativeParentComponent instanceof FeedWorkspace && str_starts_with($path, '/commits/')) {
$this->emit('open-conversation', substr($path, strlen('/commits/')), $data, $this instanceof Log);

return;
}

$uri = $this->tabRoot().$path;

if ($this->isShowing($uri)) {
Expand Down
13 changes: 11 additions & 2 deletions app/NativeComponents/Concerns/ShowsCommits.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use App\Devnull\SiteLink;
use App\Devnull\Timezone;
use App\Devnull\Toasts;
use App\NativeComponents\FeedWorkspace;
use Closure;
use Native\Mobile\Attributes\On;
use Native\Mobile\Edge\SharedValue;
Expand Down Expand Up @@ -221,7 +222,6 @@ public function toggleStar(string $id): void
return;
}

$this->showStar($id, ! $commit->starred);
$this->queueStar($id, ! $commit->starred);
}

Expand Down Expand Up @@ -490,10 +490,19 @@ public function deleteCommit(string $id): void

if ($deleted === true) {
Toasts::say('rm '.$commit->shortHash.' → /dev/null', short: true);
$this->showDeleted($id);
if ($this->nativeParentComponent instanceof FeedWorkspace) {
$this->nativeParentComponent->conversationDeleted($id);
} else {
$this->showDeleted($id);
}
}
}

public function applyWorkspaceDeletion(string $id): void
{
$this->showDeleted($id);
}

/**
* A commit you deleted leaves the screen as one deleted on its own page does
* when you come back: the screen shows what the cache kept, then asks devnull.
Expand Down
Loading