Commit Graph
273 Commits
Author SHA1 Message Date
Reynaldi ChernandoandClaude Opus 5.5 105be9a593 Fix preview host races from review
- The host the preview is on is read from the address the frame loaded,
  never from a check's answer, so a late check can't move the Share
  link, hidden tests or error mapping off the host on screen.
- A refresh takes over the early clear before its host check, so a slow
  failed check no longer stops the clear and leaves the overlay up with
  nothing reloaded.
- Opening a project points the frame at it before the check, so a badge
  sync or toolbar Reload during the check loads this project, not the
  one before, and a refresh starting meanwhile no longer cancels the load.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-13 22:15:01 +07:00
Reynaldi Chernando 54f6ff82e7 fallback logic 2026-11-13 21:06:34 +07:00
Nariman Jelveh 1b2cf27926 Restore missing projects, and stop the project list losing them (#177)
* Restore missing projects, and stop the project list losing them

A project is its chat file plus its app directory, but the sidebar lists only what chat-history/chat-list.json names. Three ways an entry was lost while the files stayed intact:

- A project created (or renamed, or pinned) while a list write was in flight was recorded as synced. The next save read it as deleted in another tab and dropped it.
- A rebuild from the chat files skipped any file it could not read at that moment, then saved the result.
- A save whose read of the list failed wrote this tab's list over whatever was on disk.

Settings has a new Projects tab (/?settings=projects) that looks for chat files no entry names and, when asked, adds their entries back. It only adds list entries, after saving and confirming a copy of the list.

* Keep keyboard focus in Settings while looking for and restoring projects

The Look and Restore buttons were disabled while they worked, which dropped keyboard focus to <body>, out of the dialog. Restoring then removed the focused Restore button, with the same result. They are aria-disabled while waiting, as the app's other busy buttons are, and focus returns to the Look button when the results are rebuilt.

* Let the Settings tab strip scroll on a phone

With a fourth tab the strip was wider than a phone screen: the Projects tab was cut off and the whole dialog scrolled sideways. The strip now scrolls on its own, tabs stay on one line, and the selected tab is brought into view, including when a link opens Settings on it. Desktop rendering is unchanged.

* Stop retrying every chat file when reads are failing across the board

Each retried read costs a second and two more requests. Rebuilding the list from the chat files while offline or throttled retried every file: with 300 projects, boot waited five minutes on it and made three times the requests. After two files have failed despite their retries, the rest of a pass are read once. Completing unverified entries in the background stops at the first file that still can't be read, and tries again at the next load.

* Keep a session's own entry over a stand-in when the list loads late

When boot could not load the list, the first save loads it. If that late rebuild could not read the file of a project made in the meantime, its stand-in entry replaced the complete one already in memory, and the project's next save took the stand-in's title and pin for its own.

* Record the synced list without parsing the written text back

The save parsed its own output to remember what it wrote, which made that step about half again as slow at 500 projects. The same map is now built from the list in the same tick the text is serialized, before the write is awaited, at the cost it had before. Adds a test that a rename or a new project made during a write survives the next save.

* Show the missing projects in one scrolling list, with Restore above it

The list had its own scroller inside the dialog's. With more than a handful of projects that cut the last row and the note under the button off, and a list with nothing focusable in it can't be scrolled by keyboard in every browser. The list now scrolls with the dialog, as the server list does, and the Restore button sits above it so it never ends up below the fold.

* Say so when a Settings link can't open Settings

If the module Settings is built on never finished loading, a /?settings= link waited fifteen seconds and then did nothing. It now opens Settings anyway, which reports that it isn't ready the same way its toolbar button does. Adds tests that run the link handler.

* Say where restored projects actually land in the sidebar

The message said restored projects were at the top of the sidebar. Once anything is pinned the sidebar is split into Pinned and Recent, and a restored project goes to the top of whichever its own pin puts it in.
2026-11-12 18:06:09 -07:00
Reynaldi ChernandoandClaude Opus 5.5 14c16e4735 build notifications (#175)
* build notifications

* Fix build notification review findings

- A turn stopped during its plan check no longer registers itself, so a
  newer turn's clarify question can't notify for the wrong project.
- A tap the service worker handles goes only to the tab already showing
  that project, or opens the project in a new tab. Other tabs are left as
  they are, so a tab still starting up or one that doesn't know the
  project no longer drops the tap.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-13 01:38:19 +07:00
Reynaldi ChernandoandClaude Opus 5.5 3535f4eaf2 add basic readme for builder projects (#176)
* add basic readme for builder projects

* Fix README review comments

- The prompt rule now lets the AI create a README when the user asks.
  The old wording read as if creating one was never allowed, so a user
  who deleted the builder's README couldn't get the AI to write one.
- A download waits for a new project's README. The first version is
  queued without being awaited, so a zip made right after a first build
  or a template copy could miss it. The download runs the same step, or
  waits for the one already running.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-13 01:30:24 +07:00
Reynaldi ChernandoandClaude Opus 5.5 7d5e7c91a8 Send the plans and dollar amount with Upgrade After Limit (#174)
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-12 19:02:26 +07:00
Reynaldi ChernandoandClaude Opus 5.5 d8a539c13a Add the fast mode prop to the usage limit analytics (#172)
Upgrade After Limit and Unblocked After Limit now say whether the build that
ran out of credits ran fast through the user's fast mode, as Usage Limit Hit
already does, so upgrades can be compared for fast and usual builds.

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-11-12 18:34:23 +07:00
Reynaldi ChernandoandClaude Opus 5.5 ba262caa8e fast mode (#169)
* fast mode

* Fast mode on every plan, and the hint after 30 seconds

- Any signed-in account can switch fast mode on. The Upgrade button in
  its panel, the free plan's Upgrade hint, the plan check at turn start
  and the Fast mode tile in the Upgrade dialog are gone.
- The long-build hint shows once a build has run 30 seconds, down from
  45, still once per browser and never on a project's first build.
- Fast Mode Opened carries on and mid_build instead of access, and Fast
  Mode Hint Shown has no props.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

* improvements

* Keep the fast mode hint and switch right on phones, clarify cards and full storage

- A phone's app view covers the chat without removing it from the layout,
  so the hint now waits for the chat view, and the hint, pointer and panel
  go when the user switches back to the app.
- The clarify card showed Send mid-turn, which told fast mode the turn had
  ended and dropped the hint's timer. It now gets the real turn state, and
  the hint waits while a clarify card waits on the user.
- With storage that reads but won't take a write (full), the switch and the
  hint's count now hold for the load instead of the old stored value.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-11-12 13:02:30 +07:00
Reynaldi ChernandoandClaude Opus 5.5 1a154234b6 Start a project from an attached zip (#171)
* Start a project from an attached zip

A user can download a project as a zip, change it on their computer or
move it to another account, then bring it back by attaching the zip. The
AI unpacks it with a new ExtractZip tool and decides where from the
request: into the working directory to start from, continue or import
the project, or into a folder under assets/ to use the files in it.

- The zip is saved to assets/ like any attachment. The note on the
  message lists what it holds (up to 25 paths, names fenced as data) and
  points the AI at ExtractZip. If the request doesn't say and the zip
  holds an index.html, the AI imports it as the project.
- ExtractZip drops one wrapping top-level folder (my-site/index.html),
  skips __MACOSX, ._ files, .DS_Store, .git and .puter, and runs every
  entry path through assertPathInProject, refusing any with "..".
- It refuses archives over 2000 files or 200MB unpacked before writing
  anything, removes the zip afterwards unless keep_zip is set, takes a
  version like any file change, and tells the AI about backend sources
  under workers/ that still need create_worker in this project.

Closes #168

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Harden ExtractZip against lying archives and late stops

Fixes from the review of #171:

- Check ".." on the name the archive gives. JSZip resolves it in
  file.name (../index.html reads as index.html) and keeps the original
  as unsafeOriginalName, so such entries were written under the tidied
  name instead of skipped.
- Unpack each entry as a stream that stops once it passes the size the
  archive declares for it. The file and byte limits trust those sizes,
  and JSZip only compares them with the real output at the end, so a
  forged size could inflate a few MB into GBs in the tab first.
- Check for a stopped turn after each wait (unpacking, the file's lock,
  the retry's pause) and before removing the archive, so nothing is
  written, and the zip isn't deleted, after the user stops the turn.
- Sort a zip into the note by the original file. A zip known only by
  its type ("download", application/zip) is saved as a ref with neither
  a .zip name nor the type, so it was listed under other files without
  its contents or the ExtractZip hint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-11 23:03:58 +07:00
Reynaldi ChernandoandClaude Opus 5.5 8c31a352c4 Track JavaScript errors in Plausible (#170)
* Track JavaScript errors in Plausible

Uncaught errors, unhandled promise rejections and script or stylesheet
files that fail to load now each send a "JavaScript Error" custom event
to Plausible, with kind, message, source (path:line:col) and the top
stack frames as props.

The tracker is an inline script right after the Plausible queue stub,
ahead of puter.js and the app bundle, so it also catches errors during
startup, including a syntax error that stops the bundle from running.
Repeats of the same error are dropped and each page load sends at most
10 events, because every event counts toward the Plausible quota.
Browser-extension errors, the ResizeObserver loop notice and failed
images are ignored. The page URL is not sent: Plausible already records
the path, and query strings and hashes are stripped from every URL that
is sent.

scripts/test-error-tracking.mjs runs the real inline script against a
fake window and is part of npm test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FWCxozGGiqtLLCiSvRCSu3

* Strip query strings and hashes from stack frames

The stack prop went out with frame URLs untouched, so an error thrown by
an inline script, whose frames carry the full page URL, sent the page's
query string to Plausible even though source was already cleaned. Every
URL in a stack now loses its query string and hash before it is used,
keeping :line:col, and the rejection's source is read from the cleaned
stack too.

The privacy test now covers query-bearing stacks in Chrome and Firefox
formats, including a query that holds ":1:2" or parentheses, an eval
frame, and a #private method name that must survive.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FWCxozGGiqtLLCiSvRCSu3

---------

Co-authored-by: Claude <[email protected]>
2026-11-11 22:42:01 +07:00
17ee6b67b3 Let the chat connect the user's own domain (#152)
* Let the chat connect the user's own domain

The AI could only send users to the Publish menu for a custom domain. Now
it calls OfferCustomDomain, which puts the Publish card in the chat in a
domain mode. Its button is the popover's Use custom domain (useOwnDomain),
so Puter's window still opens straight from the user's click, as a browser
requires of a popup.

- Before a first publish the card has the address field and "Publish with
  your domain": it reserves the name, opens Puter's window and publishes,
  as the popover's route does (publishWithDomain), with the card's own
  field, errors and publish passed in.
- On a live site it shows the address (the domain once it works) and Use
  custom domain, Change domain or Connect again, with a line while the
  domain waits for DNS. A turn doesn't hold it up; an address change does.
- On the free plan the button opens the upgrade, and the card turns into
  the paid one when the plan changes.
- The domain chosen is saved on the card's tool result, so an older card
  reads "Domain added" after a reload.
- The prompt points own-domain requests at the tool, and older chats whose
  prompt sends the user to the Publish menu get a note.

* auto pick subdomain, handling cancel, ui and copy changes

* Keep a picked domain's site publishable

From the review of the own-domain flow:
- A domain picked on a first publish that didn't land (a build started
  while Puter's window was open, it failed, or another project opened)
  shows on the chat card with Publish, onto the name the domain is on.
  An unpublished project's reserved name is also read from the domain
  saved on it, so a reload doesn't lose it.
- Plain Publish is held only while the name is reserved or let go, not
  for as long as Puter's window is open. A cancel leaves the name alone
  when a plain Publish has taken it, and a pick starts no second publish.
- The name a new one replaces is deleted only once a domain is picked.
- No "couldn't open" alert once a domain was picked, and a window error
  leaves nothing held.
- The free-plan domain card counts as an upgrade prompt.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Reynaldi Chernando <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-11-11 18:22:29 +07:00
Reynaldi ChernandoandClaude Opus 5.5 ec8ca1f900 fast mode on first build with experiment (#166)
* fast mode on first build with experiment

* Fast first build on Opus only, with build time on Build Completed

- Ask for the fast service speed only when the turn's model is Opus
  (ModelCore's fast flag), checked once the plan check settles the model.
- Build Completed reports how long the build took as `took`, in bands up
  to 10m+, so the speed experiment can show whether the fast variant was
  faster.
- Inside the Puter desktop the Low Balance cohort is 'app' even with a
  variant forced, as it was before it moved into experiments.js.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
2026-11-11 15:28:42 +07:00
Nariman Jelveh cc979e7c46 Offer Add credits to a paid account that runs out (#164)
* Offer Add credits to a paid account that runs out

A paid account that hit the usage limit was told to move to a bigger
plan, in the out-of-credits dialog and in the chat message. Puter now
sells add-on credit to accounts with a plan, which is quicker than
changing plans, so both lead with Add credits for an account that can
buy it and keep the bigger plan one click away. Free accounts, team
seats and the Puter desktop see what they saw before.

Add credits opens puter.js's top-up over the page. When credit is
bought, there or in another tab, the build that stopped continues on
its own once Puter reports credit to spend.

* Cover the Add credits announcement in the announce checks

The usage-limit message now has two wordings, which moved the original
announcement past the distance its wiring check allowed. The check
covers both.

* Wait for bought credit to land before continuing the build

A request can be refused with a little credit still left, when it costs
more than what remains. The balance read after a purchase was then above
zero before the purchase landed, and the build continued straight into
another refusal. The balance is now read when the top-up opens, and the
build continues once it has gone above that.

* Forget the stopped build once a new turn starts in its project

The project a build stopped in stayed recorded until the build was
continued. When the user sent a message themselves before the credit
landed, the record outlived that turn, and the next time the purchase was
noticed (on returning to the tab) a second, unasked-for continue was sent.
A turn starting in that project now clears the record.

* Continue the stopped build after Add credits in the chat message too

Buying credit from the chat message's Add credits button left a build the
out-of-credits dialog had recorded as stopped where it was, while the same
purchase from the dialog continued it.

* Leave a Resume in the chat when bought credit is slow to arrive

When the credit hadn't shown fifteen seconds after a purchase, nothing
happened: the top-up closed on a chat that still ended at the limit, and
the build only continued if the user asked for it in words. The chat now
gets the Resume banner, saying the credits haven't arrived yet.
2026-11-10 15:52:27 -07:00
jelveh afe0b5280e Remove the Send feedback button from the toolbar
The toolbar's feedback button (an icon beside <puter-user-menu>, a
labelled pill inside Puter) is gone, along with its click handler and
styles. The account panel's Send feedback row stays.

On phones the GitHub link now steps aside for the Settings gear instead
of the feedback icon. The one-row phone preview toolbar no longer has to
make an exception for the wide feedback pill, so those guards are dropped.
2026-11-07 11:53:35 -07:00
jelveh 35a40f0f53 Move the theme control into Settings, under MCP and Appearance tabs
With <puter-user-menu>, the toolbar's dark mode toggle now lives in the
Settings modal instead. Settings has two tabs: MCP connections and
Appearance, which holds the account panel's Light / Dark / Device control
(one shared builder, kept in sync wherever it is open). Inside Puter, where
there is no gear, the toolbar toggle stays.

The tabs are a proper tablist: only the selected tab is a Tab stop, and the
arrow keys and Home/End move between them. The gear reopens the last tab;
the account panel's MCP row always opens the MCP tab. The modal keeps one
height across tabs so switching never shifts it, and closing after a theme
change refocuses the re-rendered gear.
2026-11-07 11:44:06 -07:00
jelveh 38b7d0fd1c Add a Settings gear beside <puter-user-menu> for MCP connections
<puter-user-menu> has no row for the account panel's MCP connections, so
with it the MCP dialog could not be reached. A signed-in user now gets a
Settings gear just left of the element (not inside Puter), styled like the
other toolbar icons in both themes and on phones.

The gear opens a centered Settings modal, which for now holds the MCP
connections section; the account panel's MCP row opens the same modal. A
click on the backdrop no longer closes it: the modal jolts instead (a ring
under reduced motion), so a stray click never loses a half-typed server.
Escape and the close button still close it.
2026-11-07 11:18:05 -07:00
Nariman Jelveh abba8bf265 Name the user to the Apps embed and use puter.js's <puter-user-menu> (#161)
* Name the signed-in user to the Apps panel's embed

puter.com/embed/apps shows the puter.com session's own apps only to the
user named in its fragment (#user=<uuid>), and the recommended apps to
everyone else. The Apps panel now names the builder's signed-in user; a
signed-out builder, or a temporary account, names no one. The panel is
rebuilt when that user changes, keyed by uuid instead of username.

* Use puter.js's <puter-user-menu> for the apps launcher and account button

When the running puter.js defines <puter-user-menu>, the toolbar places it
beside the controls updateUserMenu re-renders, and the builder's own apps
launcher, avatar and account panel stand down. The element follows the
builder's sign-in through puter.js, names the user to the app browser, and
closes it when the sign-in changes. Without it, the builder's own launcher
and account panel are unchanged.

With the element, a signed-in user's theme toggle and Send feedback sit in
the toolbar, as they do inside Puter; Send feedback is an icon there, and
on phones the GitHub link steps aside so the row fits. Account settings is
no longer offered. The element's Sign In and Log out reach the builder's
usual paths: signing in sets window.user from the event before finishing
the session setup, and logging out forgets the account on this device and
reloads, the same as the account panel's Log out. Its parts take the
builder's icon, avatar and pill looks, in both themes and on phones.
2026-11-06 22:28:02 -07:00
Reynaldi ChernandoandClaude Opus 5.5 7507bb1553 Update code editor toggle placement (#159)
* Update code editor toggle placement

Make the code toggle less visible, fix saving code on iOS Safari, and stop
highlighting the AI's changes in the Code view.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Address review: View code wording, WebKit-only fresh reads, tablet header

- Toasts, the upgrade card and the AI's instructions point to View code
  (in the "…" menu on phones) instead of a Code button that is gone.
- The per-read cache buster runs only on WebKit; other engines honor
  no-cache, and a unique URL per read cost them a CORS preflight per read.
- New project stays in the header on tablets; only phones hide it.
- Switching project from the preview redraws the unsaved dot, so it can't
  follow into the next project.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-06 22:51:47 +07:00
jelveh db3cb3e26b Show the Apps launcher to signed-out users
The apps button now renders left of Sign In as well as left of the
avatar; only the hosted-inside-Puter toolbar omits it. A sign-in or
sign-out discards a built panel so the embed reloads for the new
account on next open.
2026-11-06 08:14:11 -07:00
Nariman Jelveh 337deec5d1 Add an Apps launcher to the toolbar (#158)
Signed-in users get a 3x3 apps button left of the avatar that opens a
panel showing puter.com/embed/apps. Same card, caret and dismissal rules
as the account panel; an edge-to-edge sheet on phones; the embed follows
the app's light/dark theme.
2026-11-05 17:38:05 -07:00
Nariman Jelveh f51f90e25f Show sent attachments as cards, like a pasted text card (#157)
Images and files in a sent or queued message now use the same 100px card
as a pasted text card. Images are cropped to fill theirs; files show an
icon and their name over their extension (HTML, PDF...) where a paste shows
PASTED. Replaces the tiny 50px images and the emoji file line.
2026-11-05 13:13:37 -07:00
Nariman Jelveh e765e3f5fc Turn a long text paste into a Pasted text card (#154)
* Turn a long text paste into a Pasted text card

Pasting a log, a spec or a file's contents filled the composer with a wall
of text. A paste of 2,000+ characters or 30+ lines now goes in the
attachment tray as a card instead, styled after claude.ai's: the opening
lines in small faint type over a PASTED badge, with an edit pencil and the
usual remove button. Clicking the card shows the whole text; the pencil
opens it for editing (saving it empty removes the card).

The paste is not a file: it is never saved to assets/. It is stored as a
text document block marked as pasted, so the sent message (and a reloaded
chat) shows the same card, and the model gets the whole text inline,
labelled as text the user pasted. Shorter pastes are unchanged.

* Keep a pasted text card in the composer draft

A long paste used to be composer text, so the draft saved it: a reload or a
trip to another project brought it back. As a card it lived only in memory
and was lost. The draft envelope now carries the cards (queued messages'
first, like their text), every add, edit or remove saves it, a restore puts
them back in the tray, and a send takes them out of the tray with the text.
Cards count toward the cloud mirror's size cap, and a card-only composer is
kept, not replaced, when the user signs in.

* Hand a pasted text card back when its send never lands

A composer send stopped before its message reached the conversation (Stop
while files upload) puts its text back in the box. A long paste used to be
part of that text; as a card it was dropped. The card now comes back with
the text. Files still don't: their blob URLs are about to be revoked.

* Send a pasted text card with a reply to a clarifying question

While the AI waits on clarifying questions, the composer's text is the
reply. A long paste used to be part of that text; as a card it was left out,
and a paste-only reply did nothing at all. The cards now go in the reply
after the typed text and leave the tray.

* Size the tray's file thumbnails like the pasted text card

Next to the 100px Pasted text card, a file was a 60px square with the file
icon stretched across it. Every tray thumbnail is now the same 100px
rounded card. An image fills it, with its name along the bottom; any other
file shows the file icon small and centred over its name. The marketing
pages' composer copies the app's tray, so it changes with it.

* Draw a queued message's pasted text as a card

The sent message shows a long paste as a Pasted text card, but the queued
message drew it as a plain 📄 Pasted text chip that did nothing. It now
uses the same card, and clicking it shows the text.

* Give each pasted text card its own name

A new card was numbered by counting the cards in the tray, so after
removing an earlier one the next paste reused a name still in use: two
"Pasted text 2" cards, and the model read both under the same label. A new
card now takes the lowest name not in use.

* Read a pasted text card as the user's words in titles and labels

A long paste used to be the message's text, so the project title, the
version label (and its GitHub commit message) and the naming/suggestion
transcript read it. As a card it is a document block those skipped: a
paste-only first message titled the project "New Chat" and its version
"Update". They now read the paste after any typed text.

* Announce a long paste that became a card

The paste never reaches the box, so a screen reader user heard nothing and
found the box empty. Say that it was attached, and under which name.

* Keep unsaved edits to a pasted card on a click outside

A click outside the Edit pasted text dialog closed it and threw the edits
away without a word. With unsaved changes it now keeps the dialog open and
puts the cursor back in the text. Cancel, the close button and Escape
still discard on purpose; an unchanged dialog still closes.

* Stop iOS from zooming in when the pasted text editor opens

The Edit pasted text dialog focuses its 12px text area itself, and iOS
Safari zooms the page into any field under 16px when it gets focus. On
screens up to 900px the text is 16px, as for the app's other dialog fields.

* Don't take a long paste into a read-only composer

While a send sets up, the composer is read-only and the browser drops a
short paste. A long one still went into the tray as a card, and the send's
draft clear then left it out of the draft.
2026-11-05 11:54:58 -07:00
Reynaldi ChernandoandClaude Opus 5.5 30e94e37fe show relevant ideas to developers (PUT-2334) (#156)
* show relevant ideas to developers

* Report the pill test as pill_variant on Project Created

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-05 15:14:17 +07:00
Reynaldi ChernandoandClaude Opus 5.5 e8ca765d78 whats new modal (#155)
* whats new

* Let the upgrade dialog and someone typing come before What's new

Startup leaves What's new out while an Upgrade click is pending, since the
success dialog may be about to open. If the upgrade dialog opens over it
later, What's new closes without being marked seen, so a later load shows
it again.

It also stays closed once the user has typed anywhere since the page loaded,
or focus has moved off the chat box, so it no longer takes keystrokes from
someone already at work.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-11-05 14:58:06 +07:00
Reynaldi Chernando 20c52c3615 parallel tool calls (#153)
* parallel tool calls

* handle failed tool call
2026-11-05 08:15:42 +07:00
Nariman Jelveh 8aedbc909b Cut a long address short on the popover's Also at line
Once a domain works, the popover shows the site's puter.site address on
an "Also at" line under the URL row. That line could neither wrap nor
shrink, so a long address scrolled the popover sideways: 56px on a phone
in a headless Chrome check, 7px on desktop. It now ends in an ellipsis
like the URL row above it, with the whole address in its title.
2026-11-04 13:02:24 -07:00
Nariman Jelveh 4f00370585 Find the project that had a domain from the project list
Connecting a domain takes it off any other project that had it. To find
that project, every published project's saved file was downloaded in
parallel, and each file holds the project's whole conversation, so one
connect could pull tens of megabytes. The project list now keeps each
project's domain hostname (publishedDomainHost, in any state), and only
the project listed with it is updated, with no other reads.
2026-11-04 12:58:14 -07:00
Nariman Jelveh a6c077f6fa Keep focus on a DNS record's copy button across re-renders
The popover re-renders when a turn ends or a domain check changes
something, and puts focus back on the control that had it, found by its
first class and data-action. The record copy buttons had no data-action
and share their first class with the site's Copy link button, so focus
on one jumped to Copy link at the top. Each now has its own.
2026-11-04 12:55:40 -07:00
Nariman Jelveh 5c7c612bdf Keep keyboard focus on a domain button while it waits
Check now, Connect again and Change domain were disabled while they
worked. The popover re-renders to show that, and a disabled button can't
hold focus, so focus fell back to the popover itself and a keyboard user
had to tab back to where they were. They are now aria-disabled while
busy, which keeps them focusable, and their click handlers ignore them
until they are free.
2026-11-04 12:53:56 -07:00
Nariman Jelveh 85a8e6ba27 Word the address-change warning by the domain's state
Before an address change, the warning told every user with a domain that
it "shows your site" and "stops working". A domain still waiting for
its DNS records, or one already found not working, does neither. Those
now read that the domain is set up for the current address and won't
work with the new one.
2026-11-04 12:51:36 -07:00
Nariman Jelveh 927665fafe Remove a reserved address when its project is deleted
The own-domain route reserves a project's puter.site name before its
first publish. When that publish never landed, the name is in neither
of the URLs deleteChat removes, so deleting the project left the site
on the account, holding one of its limited site slots.
2026-11-04 12:50:02 -07:00
Nariman Jelveh 830fe65d6e Use the typed address when retrying Use custom domain
After an own-domain first publish that didn't land, Use custom domain
went back to the name that try had reserved, even when the user had
typed another one, and put that name back in the field. A plain Publish
already uses the typed name. Use custom domain now does too, and removes
the reserved site it replaces so it doesn't take one of the account's
site slots. A typed name that is taken leaves the reserved one in place.
2026-11-04 12:48:25 -07:00
Nariman Jelveh 5063bf4870 Keep a reserved address in the publish field after reopening
An own-domain first publish reserves its puter.site name and connects
the domain to it before the publish runs. When that publish failed and
the user closed the popover, the next open filled the address field
with a random name. Publish then created that site and deleted the
reserved one, leaving the domain on an address that no longer exists.
The field now starts with the reserved name.
2026-11-04 12:46:25 -07:00
Nariman Jelveh 4eaca89686 Stop showing the own-domain progress note on later publishes
The popover's progress note says "Pick or add your domain in Puter's
window" while a first publish goes out with its domain. The flag behind
it stayed on afterwards, so a later publish from the chat's Publish card
showed it too when the popover was opened mid-publish. It now clears
when that publish ends, whether it worked or not.
2026-11-04 12:44:46 -07:00
Nariman JelvehandClaude Opus 5.5 43400456b6 Let a published site use the user's own domain
Rebuilt from #141 (by Reynaldi Chernando) on top of main, where publishing
now runs through publishProject (the popover and the chat's Publish card)
and address changes through changePublishedAddress.

- "Use custom domain" / "Change domain" beside the site address opens
  Puter's domain dialog (puter.hosting.attach). The free plan gets the
  upgrade dialog, which now lists the benefit.
- Before a first publish, the own-domain route reserves the name, opens the
  dialog and publishes behind it. publishProject takes the reserved name
  (ui.reserved), reuses one a failed try left behind, and holds back any
  other publish, including the chat card's, while the name is reserved.
- The popover checks a pending domain until it serves the site, and a
  connected one each time it opens. Once working, the domain is the
  address the popover, share view, copy button and sidebar show.
- Changing the address asks first when a domain depends on it. The
  question sits in changePublishedAddress, so the popover and the chat
  card both ask, ahead of the busy checks. Settings asks too.
- savePublishedFields keeps fields a caller leaves out, and changes
  domains inside the file lock, alongside main's AI note.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 12:31:09 -07:00
Nariman Jelveh c16cd87e91 Treat a control in a disabled fieldset as disabled in tests
click(), fill() and press() checked only the element's own disabled
property, which stays false inside a <fieldset disabled>. The browser
ignores a click there, so the test went on and failed later with a
misleading message instead of saying the control is disabled. They
now ask the browser, with :disabled.
2026-11-03 21:25:52 -07:00
Nariman Jelveh 921a42e8b5 Mention hidden tests in update_preview only when they're on
The update_preview description told the AI it runs AppTests' tests
even with FEATURE_FLAGS.appTests off, when the tool isn't offered. The
clause now follows the flag, like the prompt rule, so with the flag
off the description is the one from before tests.
2026-11-03 21:24:48 -07:00
Nariman Jelveh 913fbbb9eb Don't let reading an app's tests hold up update_preview
verifyPreview waited for the tests file with no limit before starting
its error watch, so a slow read delayed the check and a read that
never settled left update_preview, and the turn, waiting until the
user pressed Stop. The tests now start whenever the read is done,
alongside the watch, and a read not back within 5s of the load counts
as no tests.
2026-11-03 21:23:17 -07:00
Nariman Jelveh 6664c6478e Match a retried test by its place, not its name
The retry merge kept one result per name, so when two tests shared a
name (a copy-pasted name, or two cut to the same 120 characters) and
the first passed, the second counted as passed even though it failed
both times. Both runs go through the page's tests in the same order,
so tests are now matched by position.
2026-11-03 21:21:42 -07:00
Nariman Jelveh d42fcb2fc4 Don't pull the user back into the composer when tests end
Once a test had taken focus and it was given back, the end of the run
focused the composer again even if the user had since left it, which
on a phone brings the keyboard back up. Focus is now restored only
when the hidden frame still holds it as it is removed.
2026-11-03 21:20:52 -07:00
Nariman Jelveh 95cc4164b6 Give focus back to the visible preview, not the composer, during tests
The focus guard learned where focus was only from focusin, which never
fires when the user clicks into the visible preview. If a test then
pulled focus into the hidden frame, it was handed to the composer, so
what the user typed for their app went into the chat (and Enter could
send it). The guard now follows focus on every check.
2026-11-03 21:20:37 -07:00
Nariman Jelveh 38eb67808c Make a test's in-memory localStorage behave like a Storage
The layer was a Proxy over a plain object that sent every name to
getItem, so localStorage.hasOwnProperty('theme'), String(localStorage)
and instanceof Storage threw or lied only while tests ran, and calling
Storage.prototype.getItem on it failed. It now sits on Storage's
prototype, whose members win over stored keys as on the real object,
and the prototype's methods reach the layer.
2026-11-03 21:18:37 -07:00
Nariman Jelveh ece0d79625 Give each test its own dialogs and AI reply
dialogs and setAIReply carried over from one test to the next, so a
test that checked dialogs had length 1, or relied on the default AI
reply, passed or failed depending on the tests before it. Both are
reset when a test ends: the first test still sees dialogs from page
load, and a reply set at the top level holds for every test.
2026-11-03 21:16:32 -07:00
Nariman Jelveh afcd2d470f Read the element on screen first in a test's text()
text() took the first match even when it was hidden, so in an app
that hides its other screens text('h2') read a hidden screen's heading
instead of the one the user sees. It now reads the first visible
match, and a hidden one only when that's all there is.
2026-11-03 21:15:19 -07:00
Nariman Jelveh da2666a5ba Name an un-awaited test step's failure as the test's own
A click or waitFor the test forgot to await surfaced as an unhandled
rejection, which was reported as "The app threw an error during this
test", sending the AI to fix an app that works. The runner now marks
its steps' errors and says the step wasn't awaited.
2026-11-03 21:14:38 -07:00
Nariman Jelveh 1d3431d59b Stop a timed-out test from running on into the next one
Nothing cancels a promise, so a test that ran out of time kept
clicking, filling and sleeping through the tests after it, failing
them for no fault of the app (and one left waiting, un-awaited, failed
whichever test was running when it gave up). Each step now remembers
the test it started in and never finishes once that test is over.
2026-11-03 21:13:38 -07:00
Nariman Jelveh 77cca0f358 Keep a test's BroadcastChannel messages inside the test frame
The test frame shares an origin with the visible preview, so an app
that keeps its tabs in step with BroadcastChannel sent what a test did
to the preview, which could then save it to the user's real data.
Channels opened in the test frame now talk only to each other, and one
opened before the runner loaded sends nothing out.
2026-11-03 21:11:52 -07:00
Nariman Jelveh 3e154fb580 Match puter.kv.list patterns in tests the way puter.js does
puter.js trims a list pattern, drops a trailing * and matches it as a
prefix, but the test overlay matched it exactly, so list('todo:')
missed the todos a test had just added and a working app looked
broken. The { pattern, returnValues } form skipped the overlay and an
options object was taken as a request for values. All three now behave
as they do outside tests.
2026-11-03 21:10:10 -07:00
Nariman Jelveh 44f94cee72 Refuse live AI conversations in tests and keep the voice list
puter.ai.convo opens a billed realtime session that the placeholders
didn't cover, so a test of a voice-chat button cost real money. It is
refused now. The faked txt2speech also dropped the listVoices and
listEngines puter.js hangs on it, so a voice picker broke only under
test; the fakes keep them.
2026-11-03 21:09:14 -07:00
Nariman Jelveh db14120349 Refuse email, notifications and team changes in tests
A hidden test that clicks a Send or Remind me button could send a
real email, push a real notification to the user's devices, or reset
a team member's password through puter.js. Those are refused like the
other writes, and the AI is told.
2026-11-03 21:08:30 -07:00