Skip to content

Attach photos, start polls and preview from the composer, and vote on polls - #24

Merged
simonhamp merged 8 commits into
mainfrom
feat/composer-media
Oct 22, 2026
Merged

simonhamp merged 8 commits into
mainfrom
feat/composer-media

Conversation

@simonhamp

@simonhamp simonhamp commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

The composer sheet gets what the web's composer has: photos with descriptions, a poll, and a preview. Polls in lists can be voted on too, which the app couldn't do before.

It needs four API PRs that aren't merged yet: images (nunomaduro/dev-null.sh#660), polls (#662), votes (#663) and previews (#664). Against production as it is, those buttons get a 401 or a 404 back.

What changed

  • The sheet has a toolbar pinned to its bottom, so it stays just above the keyboard: photos from the library, a photo from the camera, a poll, and the preview on the left, and stash@{0} and the counter on the right. Scheduling isn't in here; that's another branch.
  • Photos come through the official camera plugin (nativephp/mobile-camera, new). Up to three, each checked the way devnull checks them (JPEG, PNG or WebP, 10 MB, 6000 pixels a side), with the web's error messages. A kept photo is moved into the app's storage (DraftImages) and deleted once it's sent, taken off, or the sheet closes. Each one can have a description, through the web's ALT badge. A commit with photos is signed as multipart with the photos' SHA-256s, which is what #660 asks for, and goes from the background with Commit spinning, since uploads take a while.
  • Polls work like the web's editor: two options to start, up to four, 50 characters each, and 1 hour to 7 days. A commit has photos or a poll, not both, and a reply never has a poll.
  • Preview swaps the field for the draft drawn as a commit: the body, the photos, the poll's options with how long it'll run, and the quoted commit. It shows straight away, then takes the body devnull sends back from POST /commits/preview, with emoji replaced and code and mentions found. If devnull can't be reached it keeps the body as written.
  • Replies: the reply bar has no room for any of this, so it gets an expand button that opens the reply in the same sheet, with the commit being answered as a card above the field. The draft goes back to the bar if the sheet closes unsent.
  • Voting: on someone else's open poll you tap an option, then Vote, as on the web, since votes are final. The poll then shows its results here and in every cached copy.

Worth checking

  • The camera plugin is pinned to fix/present-over-sheets (NativePHP/mobile-camera#17). The plugin presented its pickers from the root view controller, which can't present while our sheet is up, so the buttons did nothing. That branch is also main, which has the HEIC to JPEG conversion that 1.0.4 doesn't. Without it a real iPhone's library photos would be refused. A 1.0.5 release would let the app go back to a tag.
  • The plugin adds microphone and location usage strings to Info.plist for features the app never calls. The camera and photo library strings are overridden in config/nativephp.php with our own wording.
  • Photos aren't kept with the stashed draft, unlike the web, which keeps them in IndexedDB. Closing the sheet drops them. That felt like a separate change.
  • WritesInBackground::writeInBackground() now goes through a new sendInBackground(), so a write can be signed some other way and given a longer timeout. SignedRequest and FetchInBackground carry a content type for that.
  • This shares commit-sheet.blade.php and OpensCommitSheet with the scheduling and slash command branches. The tools row is its own partial (composer-tools), which is probably where the schedule button should end up.

Testing

896 tests pass and Pint is clean. New tests cover the image store's checks, signing a multipart commit, polls in the JSON body, the photo, poll and preview flows in the sheet, the reply sheet, and voting.

On an iOS 27 simulator, signed in through a tunnel to a devnull running all four API branches:

  • picked three photos from the library, one of them a HEIC sample that arrived as JPEG, described one, and committed. devnull stored three WebP images with the description, and the pending commit's images loaded through the temporary URLs from #660.
  • made a poll with three options and a 3-day length, previewed it (:rocket: came back as 🚀 from devnull), and committed it.
  • replied to a commit with a photo from the expanded reply sheet.
  • voted on another user's poll and saw the results.

The camera itself can't be tried on a simulator. Screenshots 43 and 45 were taken before I stopped the reply sheet marking the draft it was handed as stash@{0}.

The last two show the pinned toolbar, checked after merging main, against a local devnull.

Closes the idea board's to-do #309 (the composer part; scheduling is separate) and covers #295's pending images.

🤖 Generated with Claude Code

…ote on polls

The composer sheet gets the web composer's tools on a row under the field:
photos from the library, a photo from the camera, a poll, and a preview.

Photos come through the official camera plugin, up to three, each checked
the way devnull checks them and moved into the app's storage until the
commit goes. Each can have a description. A commit with photos is signed
as multipart with the image hashes, the way the API asks, and sent from the
background, so Commit spins while it uploads.

A new commit or a quote can have a poll instead, with two to four options
and a length, as on the web. The preview shows the draft as the commit it
would become, and fills in the body devnull sends back for it.

A commit page's reply bar has no room for these, so it gets an expand
button that opens the reply in the same sheet, and the draft goes back to
the bar if the sheet closes unsent.

Polls in lists can now be voted on: pick an option, then Vote.

The camera plugin is pinned to a branch until NativePHP/mobile-camera#17
is released, since its pickers did nothing when opened from a sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simonhamp and others added 7 commits October 21, 2026 01:02
Commit, the reply and the tools now follow main's rules: a commit fits by
Commit::fits(), with photos it may have no body at all, and the sheet's
own sheetPosting flag covers photo uploads too. The sheet keeps main's bar
with Cancel and Commit, and issue drafts get no tools, as on the web. The
reply bar's expand button sits beside main's Liquid Glass field.

The Log's row cache now keys rows on the option picked on a poll and on a
vote being under way, so a row redraws when either changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tools, stash@{0} and the counter now sit in a bar under the scroll
view instead of at the end of it, so they stay just above the keyboard
while you write, and above the home indicator when it's down.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ShowsCommits' docblock now lists both Pin to profile, from main, and
voting on a poll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sheet's bar takes main's round cross and check. The check is enabled
by the draft's photo and poll rules (Draft::canCommit), reads Reply in the
reply view, and the title still says Reply to @handle there. The composer
tests now read the check through main's sheetButtonEnabled helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Client keeps both commitPayload() and main's answeredByDevnull(). Photo
uploads go through the same background path as other writes, so they get
main's check that devnull really answered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
composer.json takes main's mobile-browser ^1.2 beside the camera plugin's
branch, and the lock file has both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FetchInBackground gained image prefetch arguments on main, so the
multipart content type now goes last and the write dispatch passes it
positionally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp marked this pull request as ready for review October 22, 2026 15:41
@simonhamp
simonhamp merged commit 3e2152e into main Oct 22, 2026
1 check passed
@simonhamp
simonhamp deleted the feat/composer-media branch October 22, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant