Commit Graph
1746 Commits
Author SHA1 Message Date
Juan Castro eb1ad240f2 docs(notifications): describe the confirmation step as what happens; one-line comments and neutral fixtures 2026-10-28 10:06:55 -04:00
Juan Castro d40cc499f5 fix(notifications): enable() rejects on a revoke, onPush() syncs quietly, one permission check per app a minute
enable() no longer opens Puter's prompt after a push_revoked error; apps call requestPermission() from a click and enable() again. onPush()'s page-load sync no longer reports push_unavailable or push_revoked. Hidden-tab banners for an app's messages check the app's push permission once a minute instead of once per message.
2026-10-28 10:06:55 -04:00
Juan Castro 06ef9506fa Merge juancastro/put-2258-push-from-app-subdomains into juancastro/put-2262-web-push 2026-10-28 10:01:02 -04:00
Juan Castro 5513c23042 Merge juancastro/put-2172-notifications-settings into juancastro/put-2258-push-from-app-subdomains 2026-10-28 09:46:54 -04:00
Juan Castro 8aade99cc8 Merge juancastro/put-2171-push-service-worker into juancastro/put-2172-notifications-settings 2026-10-28 09:45:57 -04:00
Juan Castro 6f9719fd65 Merge juancastro/put-2170-puterjs-notifications into juancastro/put-2171-push-service-worker 2026-10-28 09:45:51 -04:00
Juan Castro 8482ff2167 Merge juancastro/put-2251-let-apps-send-notifications-to-their-own-user into juancastro/put-2168-let-users-decide-which-apps-can-send-them-pushes 2026-10-28 09:45:31 -04:00
Juan Castro b42f2acf06 Merge main into juancastro/put-2251-let-apps-send-notifications-to-their-own-user 2026-10-28 09:44:38 -04:00
Daniel Salazar 1aa7caf44c fix(notifications): visible sender for app messages; local banners follow push consent
An app title of only invisible or blank-filler characters no longer leaves
an app message without a sender; it falls back to the app's name, then its
uid. Hidden-tab banners now show only while push is on in this browser, and
for an app's message only when the app holds notifications:push.
2026-10-28 01:06:54 -07:00
Daniel Salazar 40253ae12b feat(notifications): onPush and enable with Web Push on the app's own origin
puter.notifications.onPush(key, handler) handles an app's notifications for
one channel: live over the events socket while the app is connected, and as
a browser push otherwise. enable() asks for notification permission on the
site's origin and subscribes through the site's worker, which imports
push-sw.js (served at js.puter.com/v2/push-sw.js). A new subscription is
confirmed by a challenge push the worker hands back to the page. disable()
removes it.

The GUI uses the same flow through /sw.js when the server publishes
pushWebPushKey.
2026-10-28 01:05:36 -07:00
Juan Castro fe58213bb5 fix(gui): one enable-push dialog at a time, only for apps that may send pushes
The desktop and the popup show the enable-push dialog only when the
asking app holds notifications:push. The IPC branch answers a second
call unavailable while a dialog is up, as the feedback dialog does. The
silent registration on boot and app launch runs once at a time. A popup
whose opener cannot be attested answers the enable-push request before
it closes.
2026-10-27 16:48:07 -04:00
Juan Castro 6c35780d59 fix(gui): wait for the browser's prompt before closing the permission popup
The deadline on a registration begun from a click counts no time while
the browser's own notification prompt is open, in the permission popup
and in the enable-push dialog. The enable-push dialog keeps one
registration in flight, settles on whenever it resolves while open, and
offers a retry only after a real failure. A permission dialog that ends
in a denial after Allow began a registration turns push back off and
restores the opt-out it cleared.
2026-10-27 16:48:07 -04:00
Juan Castro 415c179770 feat(sdk): puter.notifications.enablePush() and the enable-push popup
An app on its own domain can offer to turn push on in the user's browser
from its own button: puter.notifications.enablePush() opens a popup on
the Puter origin whose single button supplies the click the browser's
prompt needs, and inside the desktop the same dialog is shown over IPC.
The popup hands the opener no token. Docs for the new call and for the
paths that now call the push client's enable().
2026-10-27 16:27:23 -04:00
Juan Castro feb0937b6d feat(gui): register this browser for pushes again where it already allows them
On desktop and dashboard boot, and when an app that holds
notifications:push is launched, a browser whose notification permission
is already granted registers again without a click, unless the user
turned push off in this browser.
2026-10-27 16:27:23 -04:00
Juan Castro f880544ebe feat(gui): turn on browser push when an app is allowed to notify
Allow on a permission request that includes notifications:push also
turns push on in this browser, from the same click, when it is off here.
The decision never depends on it. The permission popup starts the
service worker for such a request, answers the opener first and closes
once the registration is done. The Notifications tab records a browser
the user turned off, per user.
2026-10-27 16:27:23 -04:00
Neal Shah 25a574b57d GUI for custom domains (#4355)
Notify HeyPuter / notify (push) Canceled after 0s
release-please / release-please (push) Canceled after 0s
Backend Tests / changes (push) Canceled after 0s
Backend Tests / typecheck (push) Canceled after 0s
Backend Tests / test shard 1/4 (push) Canceled after 0s
Backend Tests / test shard 2/4 (push) Canceled after 0s
Backend Tests / test shard 3/4 (push) Canceled after 0s
Backend Tests / test shard 4/4 (push) Canceled after 0s
Backend Tests / test (main) (push) Canceled after 0s
Backend Tests / coverage (push) Canceled after 0s
Maintain Release Merge PR / update-release-pr (push) Canceled after 0s
* PUT-2226

* modifications'

* routing fixes

* secondary custom domain verification
2026-10-27 15:54:02 -04:00
Juan Castro 389fd26bee fix(gui): turn on can't run twice at once
The browser card's button stays out while the client is enabling or
disabling, through any redraw in between, and a status read begun before
the press no longer draws the card.
2026-10-27 14:20:17 -04:00
Juan Castro d77ebadfe6 Merge juancastro/put-2171-push-service-worker into juancastro/put-2172-notifications-settings 2026-10-27 14:18:31 -04:00
Juan Castro 7e78a4feef fix(gui): the service worker never reuses cached imports
The worker is registered with updateViaCache set to none, so the scripts
it imports are fetched fresh on every update check.
2026-10-27 14:18:07 -04:00
Juan Castro 7aa4d91ae9 fix(gui): a tap delivered twice routes once
The page remembers the notifications it handed over in the last ten
seconds and drops a repeat, whichever way it arrives.
2026-10-27 14:17:57 -04:00
Juan Castro 5f7ab80721 fix(gui): local banner tags carry the app
An app message's group is tagged under its app, as the server tags a
push, so two apps using the same group name keep separate banners.
2026-10-27 14:17:48 -04:00
Juan Castro b3dbfdbd7b fix(gui): route push taps through the stored notification
A tap from the worker names only a notification; the desktop and the
dashboard read the signed-in user's own row and route from its app,
target and data, as a tap from the URL already did. Without a row the
dashboard opens the list and the desktop does nothing.
2026-10-27 14:17:40 -04:00
Nariman JelvehandClaude Opus 5.5 241fb7be8a feat(dashboard): simplify the share modal
- The access level and Share button appear beside the field only once
  someone is entered, instead of a full-width button that sat disabled.
- The access list sits directly under the field. Each person's menu ends
  with Remove access (or Cancel invitation), replacing the trash column;
  the inline confirmation stays.
- General access is one row: a lock or globe icon, the switch with its
  explanation beneath, and the level on the right.
- A footer holds Copy link and Done. Copy link is offered for any single
  item, since the link opens it for anyone who has access, and confirms
  in place. Status messages live in the footer; successes fade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:08:44 -07:00
Juan Castro c13bff0563 fix(gui): removing this browser also deletes it on the server when its local record is gone 2026-10-27 11:10:32 -04:00
Juan Castro 2f94ee1088 fix(gui): removing this browser in Settings also forgets its push registration 2026-10-27 11:07:23 -04:00
Juan Castro 3068059058 Merge juancastro/put-2171-push-service-worker into juancastro/put-2172-notifications-settings 2026-10-27 11:06:38 -04:00
Juan Castro 7ee0cbfac9 feat(gui): the service worker tells open tabs when the push subscription changes 2026-10-27 11:06:14 -04:00
Juan Castro dd44c0704a Merge juancastro/put-2171-push-service-worker into juancastro/put-2172-notifications-settings 2026-10-27 10:44:19 -04:00
Juan Castro 681b9cd463 Merge juancastro/put-2170-puterjs-notifications into juancastro/put-2171-push-service-worker 2026-10-27 10:44:19 -04:00
Juan Castro 9fb66137a3 Merge juancastro/put-2251-let-apps-send-notifications-to-their-own-user into juancastro/put-2168-let-users-decide-which-apps-can-send-them-pushes 2026-10-27 10:44:14 -04:00
Juan Castro 057aa116a4 Merge origin/main into juancastro/put-2251-let-apps-send-notifications-to-their-own-user 2026-10-27 10:44:11 -04:00
Daniel Salazar 6c78d2f9e9 fix(ui): reject failed desktop IPC requests (#4378) 2026-10-26 22:14:19 -07:00
Daniel Salazar f1964778b4 fix(puter-js): coordinate sign-in callbacks and filesystem sockets (#4375) 2026-10-26 22:09:52 -07:00
Daniel Salazar c8f7b1ceda fix(auth): support recovery codes when disabling two-factor auth (#4374) 2026-10-26 22:07:52 -07:00
Daniel Salazar 0411a92f4e perf(dashboard): share launch-app requests and size recent icons (#4373) 2026-10-26 22:07:33 -07:00
Daniel Salazar 5cfd4d1f53 fix(hosting): honor user-granted subdomain permissions (#4372) 2026-10-26 22:07:04 -07:00
Daniel Salazar e1a36215f6 fix(sharing): preserve team grants and scope link invalidation (#4371) 2026-10-26 22:06:16 -07:00
Daniel Salazar bbc056cc49 fix(hosting): track trashed roots and cached backing misses (#4363) 2026-10-26 21:58:05 -07:00
Nariman Jelveh 9e1ccf7ff3 fix(dashboard): ninth adversarial review of Files — bugs, UX, a11y, perf (#4356)
Maintain Release Merge PR / update-release-pr (push) Canceled after 0s
Backend Tests / changes (push) Canceled after 0s
Backend Tests / typecheck (push) Canceled after 0s
Backend Tests / test shard 1/4 (push) Canceled after 0s
Backend Tests / test shard 2/4 (push) Canceled after 0s
Backend Tests / test shard 3/4 (push) Canceled after 0s
Backend Tests / test shard 4/4 (push) Canceled after 0s
Backend Tests / test (main) (push) Canceled after 0s
Backend Tests / coverage (push) Canceled after 0s
Notify HeyPuter / notify (push) Canceled after 0s
release-please / release-please (push) Canceled after 0s
* fix(dashboard): keep Files keys away from the rows under a publish dialog

With Publish as website or Publish as Worker open and focus on one of its
buttons, the arrow keys moved the listing's selection behind the dialog,
Ctrl/Cmd+A selected every row, and Delete trashed whatever was selected.
The Files shortcuts already stood aside for the share and properties
modals; they now do for the dashboard dialog overlay too.

* fix(dashboard): let type-to-select in Files find names with spaces

Typing "my z" to reach "my zebra.txt" stopped at "my notes.txt": the space
went to the image preview instead of the name being typed, and the search
went on as "myz". A space typed while a name is being typed now belongs to
it; on its own, Space still opens and closes the preview.

* fix(gui): say so when a rename can't reach the server

A rename that failed on the network (offline, connection dropped) put the
old name back without a word, so in Files it looked as if the rename had
been ignored; only failures the server answered with a message were
reported. Every failure is now reported, with the unknown-cause message
when there is no other. The rename's own promise was also left to reject
unhandled on every failure, conflicts included; the error callback already
handles it.

* fix(dashboard): show a grid tile's full name on hover

A tile name cut short to fit shows in full on hover, but in the grid views
the hover never reached it: the badge layer is the size of the whole tile
and sat on top of the name. The name now sits above that layer, so its
tooltip shows; clicking and dragging by the name behave as before.

* fix(dashboard): make the empty-folder notice readable in dark mode

"No files in this directory." and "This folder couldn't be opened." were
styled inline with no color of their own and inherited the page default,
black, which on the dark theme's background could hardly be seen. Both now
share a class with the muted text color the Apps tab's empty state uses,
in either theme.

* fix(dashboard): let a folder be published again once its website is removed

Disassociating a folder's website in Properties took the globe badge off
its row, but the row's entry still said it had a website: its menu kept
Publish as website greyed out, and going into another folder and back
brought the badge back. The entry the menu and the listing read is now
updated with the row, and the sites cache is refreshed, as publishing does.

* fix(dashboard): keep a just-published worker's badge across navigation

Publishing a .js file as a worker badged its row, but the row's entry still
listed no worker. Going into a folder and back redrew the row from that
entry (the folder's cached listing), so the badge went away and the menu
offered Publish as Worker again, which would deploy the file a second time.
The entry now records the worker, as publishing a website records its site.

* fix(dashboard): badge a folder published from inside it once you go up

Publishing the folder on screen (from the listing's background menu) and
going Up showed its row without the globe badge, and with Publish as
website on offer again: the parent's listing came from puter.js's cache,
and adding a website sends no fs event to clear it. Removing a website from
inside the folder (Properties > Disassociate) left the badge behind the
same way. Both now drop the parent folder's cached listing.

* fix(dashboard): don't offer a second website for a published folder

A folder's own row greys out Publish as website once the folder has a
site, but its menu on the sidebar, a breadcrumb or the listing's
background kept offering it, and publishing again gave the folder a second
address. Those menus now check the user's sites as well.

* fix(dashboard): give Files' row menu button a finger-sized target on tablets

On a touch screen with room for the full list columns (an iPad in
landscape, say), a row's '⋯' was a 20x23px target right beside a row whose
tap opens the item, so a near miss opened the file instead of its menu.
On touch it now answers across the row's height and 44px wide; phones'
two-row layout keeps its 44px square, and mouse pointers are unchanged.

* fix(dashboard): keep Files' Back arrow clear of the phone menu button

On phones the breadcrumb row was shorter than the fixed menu button beside
it, so the button hung over the arrows below, and its enlarged tap area
took the top quarter of Back: a tap there opened the menu. The crumb row
is now as tall as the button, which also lines the crumbs up with it, and
on touch the crumbs themselves get a taller tap target.

* fix(dashboard): fit the Files image preview to the image and the listing

The Space preview was a strip the full width of the listing whatever the
image's size (a 64px icon sat in a card 900px wide), and at 3px from each
edge rather than the 20px it was given, its padding coming on top of its
width. In a short listing (a phone in landscape) it ran past the bottom of
the screen and cut off the file name. The card now wraps the image, stays
20px inside the listing both ways, and shrinks the image to keep the name
in view.

* fix(dashboard): let the last grid tile scroll clear of the phone footer

On a phone with a home indicator (a web app on the home screen, or Safari
with its bars collapsed) the Files footer grows by the bottom safe area.
The list makes room for that, but the grid views clear the footer with a
fixed 30px margin, so the bottom 17px or so of the last tile stayed behind
the footer even scrolled to the end. The grids' margin now grows with the
footer; select mode already made room for its bar and is unchanged.

* fix(dashboard): don't claim a large shared selection is restricted

Sharing more than 50 items skips listing who has access, but General
access still said "Restricted: only people with access can open these
items" without having looked, though some of them may be open to anyone
with the link. Choosing Restricted to close them did nothing either, since
it was already the selected option. It now says the link access wasn't
checked, and either choice applies to every item.

* fix(dashboard): don't let "Add to all" lower access on an item it couldn't list

Sharing several items lists who has access to each. When one item's
listing failed, the person's row counted that item as one they couldn't
reach and offered "Add to all", which granted it at the row's mode: someone
who could edit that item was quietly brought down to view. "Add to all"
now extends a grant only to items whose access was listed.

* fix(dashboard): let a click through as soon as a Files dialog closes

The share, properties and publish dialogs fade out for 200ms after they
close, and while they did, their invisible backdrop still took the
pointer: a click on a row right after Escape or Close was lost. The
backdrop now takes clicks only while the dialog is showing.

* fix(dashboard): don't make a removal under way look cancelled on Escape

In the share dialog, Escape on an open "Remove access" confirmation puts
the person's row back. Pressed after Remove was clicked, while the request
was still out, it did the same: the row came back as if the removal had
been called off, and then vanished when it finished. Escape now leaves a
removal that is under way alone.

* fix(dashboard): keep Share down while a share is on its way

Editing the name in the share dialog while a share was still being made
enabled the Share button again, so Enter sent a second share alongside
the first; the first one's end then took the spinner off while the second
was still running, and the two results raced for the status line. The
button now stays down until the share in flight is done.

* fix(gui): find the web address of files inside a published folder again

Files under a folder served as a website carry a link badge and their
address on the site. Working that out read `dir_path` and `address` off
each of the user's sites, which the hosting list no longer returns, so no
file ever had one. In Files the badges publishing just put up were gone at
the next listing. The folder and address are now read from the site's
root_dir, where the list carries them.

* fix(dashboard): show the link badge in the dark list view

In dark mode the list view drew a file's link badge (it is served by a
published website) as its black glyph on the dark row, where it couldn't
be seen. The grid view already gave it a light disc; the list view now
draws the glyph light.

* fix(dashboard): let Undo take back a paste of something cut from its menu

A row menu's Cut puts the item's bare path on the clipboard, where the
Files keys and the selection bar put { path, uid }. Pasting it moved the
item but recorded nothing for Undo (and didn't point Back/Forward at a
moved folder), so Ctrl/Cmd+Z then undid whatever came before, a rename
say. A bare path is now taken as an item like the rest.

* fix(gui): undo one action per Ctrl/Cmd+Z

The Undo shortcut sits in a handler bound to both keydown and keyup, and
ran on each: letting Z go before Ctrl/Cmd, as one usually does, undid a
second action straight after the first. In Files that could put back a
rename or delete an upload the user never meant to touch. It now runs on
the key going down only.

* fix(dashboard): re-sort and re-icon a Files row renamed elsewhere

A file or folder renamed in another tab, on another device or in a desktop
window took its new name in the listing but kept the place its old name
sorted to, and the icon of its old type (notes.txt renamed to notes.png
still showed a text icon). A renamed folder also left Back/Forward on its
old path. Such a row is now handled as a rename made in Files is.

* fix(dashboard): stop failed and cancelled Files uploads throwing uncaught

An upload from Files reports a failure in its progress window, through the
error callback, but the upload's promise also rejected with nobody
listening, so every failed or cancelled upload logged an uncaught error. It
is now handled there.
2026-10-26 17:21:35 -07:00
Juan Castro 2ad49e1040 Merge juancastro/put-2171-push-service-worker into juancastro/put-2172-notifications-settings 2026-10-26 18:50:14 -04:00
Juan Castro 57349a6982 fix(gui): keep the service worker on servers without push
Unregistering /sw.js wherever pushEnabled is unset ended the browser's
push subscription, so a single page load on such a server stopped pushes
from every server. Turning push off already ends the subscription.
2026-10-26 18:50:12 -04:00
Juan Castro ac0de9df44 feat(gui): notifications settings read a missing push service as unavailable
Without a push extension the /push routes answer core's plain 404. The
GUI maps that to push_unavailable, so the tab says pushes aren't
available on this server instead of reporting a load failure, and a
device removal only counts as done when the device itself is missing.
The push client doc describes the payload sw.js reads and where the
push endpoints come from.
2026-10-26 16:45:28 -04:00
Juan Castro 7d8711bf5b feat(gui): service worker that shows pushes and routes taps
sw.js shows each push as a notification and, on a tap, focuses or opens
a Puter window and hands it the tapped notification. The desktop routes
the tap to the app the notification names, opening it when needed, or
to the notifications panel. The worker is registered only when the
pushEnabled gui param is true.
2026-10-26 16:45:28 -04:00
Juan Castro af13c33678 feat(gui): notifications settings for push devices and apps
Settings -> Notifications turns pushes on and off for this browser
through a push client an extension registers, lists the user's devices
and the apps allowed to push, and switches Puter's own pushes. The tab
shows only when the pushEnabled gui param is true. The sessions window
shares the user agent formatting with it.
2026-10-26 16:45:28 -04:00
Juan Castro c79b3edf4f Merge juancastro/put-2251-let-apps-send-notifications-to-their-own-user into juancastro/put-2168-let-users-decide-which-apps-can-send-them-pushes 2026-10-26 13:12:24 -04:00
Juan Castro d366861321 Merge origin/main into juancastro/put-2251-let-apps-send-notifications-to-their-own-user 2026-10-26 13:12:23 -04:00
Daniel Salazar f850155411 KV share handles: hardening, higher limits, batch share, uninstall data deletion, account-deletion cleanup (#4344)
Maintain Release Merge PR / update-release-pr (push) Canceled after 0s
release-please / release-please (push) Canceled after 0s
Notify HeyPuter / notify (push) Canceled after 0s
Backend Tests / changes (push) Canceled after 0s
Backend Tests / typecheck (push) Canceled after 0s
Backend Tests / test shard 1/4 (push) Canceled after 0s
Backend Tests / test shard 2/4 (push) Canceled after 0s
Backend Tests / test shard 3/4 (push) Canceled after 0s
Backend Tests / test shard 4/4 (push) Canceled after 0s
Backend Tests / test (main) (push) Canceled after 0s
Backend Tests / coverage (push) Canceled after 0s
* fix(kv): sign share handle ids and drop retired handles from every node's cache

New handle ids carry a short HMAC (kvh-<uuid>.<mac>); an id whose mac
does not verify is refused before any lookup. Unsigned ids keep working
but no longer fall back to the primary on a replica miss.

Retiring or changing a handle now broadcasts outer.pubsub.kv.share.forget
so every node drops its cached row. Event subscribes and delivery
rechecks refuse a handle whose owner is suspended, as the driver does.

* fix(kv): an app its user removed stops reaching shares

An app acting for a user exercises a share grant only while the user
still has the app authorized: at least one user-to-app permission row,
which every app token path leaves and revoke-user-app '*' removes. The
share itself stays; authorizing the app again restores access. The
answer rides the permission cache, keyed by the app-under-user
generation the revoke bumps, so live subscriptions go quiet too.

* feat(kv): list keys through a watch-level share handle

list with as: 'keys' through a handle needs only watch, which already
learns every key written in the region. Values and entries still need
read.

* feat(kv): filter share listings by app and by state

GET /kv/shares and GET /kv/shares/with-me take an optional appUid: an
app uid, or null for the app-less store. An account session may name
any app; an app is refused any namespace but its own. GET /kv/shares
also takes state=live|revoked|all, all by default.

* feat(puter.js): appUid and state options on the KV share listings

listShared() and listSharedByMe() pass appUid (null for the app-less
store), and listSharedByMe() passes state. Docs cover the new options
and that a watch share can list keys without their values.

* feat(kv): indexes and store counts for share-handle quotas and retention

Adds covering indexes for the owner's live count, the owner-holder pair,
app-filtered listings and retired rows, plus store methods to count a
pair's live handles and delete rows retired before a cutoff. The live
handle cache holds up to 50,000 entries; the miss cache stays at 10,000.

* feat(kv): higher share-handle caps, a per-holder cap, and sharing with several recipients

Live share handles per account go to 25,000 paid / 5,000 free, and per
app to 10,000 / 2,000. One owner may hold out 256 live handles to one
holder on every plan, which bounds the rows a revoke or unshare walks.
Re-sharing an existing region takes no slot and is never refused.

POST /kv/shares takes recipients (1 to 50) as an alternative to
recipient: one region, level and label for all. Caps and a new
per-recipient grant budget (300/min, 2,000/hour) are checked for the
whole batch up front; failures about one recipient are reported on its
item. The single-recipient request and response are unchanged and run
through the same code path.

* feat(kv): delete share handles 30 days after they stop

A daily job deletes retired share-handle rows older than 30 days, in
batches of 500. One node runs it per day; it stops on shutdown.

* feat(puter.js): share a KV prefix with several users in one call

puter.kv.share() takes an array of recipients, positionally or as
recipients in the options form, and resolves to one item per recipient:
the share, or an error with its message and code. The single-recipient
forms are unchanged.

* feat(kv): delete an app's data on uninstall, and close account-deletion gaps

Uninstall:
- POST /auth/revoke-user-app takes an optional `deleteData` boolean, only
  with `permission: '*'` (400 otherwise). AuthService.uninstallApp revokes
  every grant, deletes the app's worker sessions, then awaits
  `app.uninstalled` { userId, userUuid, appUid, deleteData }.
- With deleteData, KvShareService retires the handles the user shares out
  of that app's namespace (as the platform, in batches) and then empties
  the namespace in the background, 5,000 items per step, stopping on
  shutdown. Handles shared with the user are not touched.
- SystemKVStore.purgeNamespace(namespace, { maxItems }) deletes up to
  maxItems items of one `v1:<uuid>:<appUid>` namespace, reading keys only,
  with no mutation events or usage, and reports `done` once it is empty.
  It refuses the system namespace and anything else. flush shares its
  page-delete path.
- puter.perms.revokeApp(appUid, '*', { deleteData: true }); the desktop's
  uninstall dialog has an unchecked "Also delete this app's data" option.

Account deletion:
- EventsService settles each of a deleted account's apps on `user.delete`
  (they cascade without `app.changed`) and drops the owner's subscription
  keys.
- The flat permission entries the account held are purged in pages.

Suspension:
- `user.suspended` / `user.unsuspended` events; the share-handle store
  drops cached rows owned by that user here and on other nodes.

* fix(permission): await a peer's flat-permission deletes so emitAndWait sees them applied

* fix(kv): strongly consistent purge pages, and an uninstall purge stops once the app is authorized again

A stale empty page could end a purge as done while items remained. The
background purge after an uninstall now checks before each step that the
user hasn't authorized the app again, so data written after that is kept.

* fix(kv): flush keeps eventually consistent page reads; only a purge reads strongly consistent

* fix(user): emit user.suspended and user.unsuspended when the user store changes an account's suspension
2026-10-26 09:43:55 -07:00
Juan Castro 759f1a798d Merge origin/juancastro/put-2251-let-apps-send-notifications-to-their-own-user into juancastro/put-2168-let-users-decide-which-apps-can-send-them-pushes 2026-10-26 11:30:50 -04:00
Juan Castro b57fef8c9d Merge origin/main into juancastro/put-2251-let-apps-send-notifications-to-their-own-user 2026-10-26 11:29:13 -04:00
Nariman Jelveh 21846192d1 feat(dashboard): publish websites and workers from Files in dashboard dialogs (#4342)
* feat(dashboard): publish websites and workers from Files in dashboard dialogs

The Files tab's Publish as website and Publish as Worker actions open
dashboard form dialogs instead of desktop windows: the address field shows
the domain inline and shapes the typed name into a valid one, workers get a
Sandboxed switch, and success shows the live address with copy and open.
Published rows get their badges right away.

The form dialog gains suffix and switch fields and a shared copy row, which
the API token dialog now uses too. The save-account / confirm-email gate
before publishing moves to one helper shared by both flows.

* fix(gui): ask a just-saved account to confirm its email before publishing

Saving a temporary account can leave its email unconfirmed when signup
doesn't require confirmation. The publish gate returned right after the
save, skipping the confirm-email step the old inline checks still ran.

* fix(dashboard): link a published site to the address the server reports

The success screen built https://<name>.<hosting domain>, which is wrong on
a self-hosted instance serving sites over http. Use the address the create
response carries, the one the rest of the GUI shows, and fall back to the
built one only when it's missing.

* fix(dashboard): dim a suffixed field as one box while the form submits

Only the input inside it dimmed, leaving the domain after the name at full
contrast while the field was locked.

* fix(dashboard): point a taken or reserved publish name at the address field

The server's reason showed in the status line, but the field stayed
unmarked and focus stayed on Publish, so changing the name took an extra
click. Mark the field invalid and focus it for the rejections that mean
"pick another name".

* fix(dashboard): type into a suffixed field when its suffix is pressed

The domain after the name is drawn inside the field's box but was a plain
span, so pressing it focused nothing. A press there now focuses the field
with the caret at the end, and the suffix shows a text cursor.

* chore(dashboard): drop a phone width rule the icon buttons never used

The form dialog's phone flex rule sizes these buttons, so the width never
applied; touch screens get 44px from the coarse-pointer min-width.

* fix(dashboard): read an underscore in a website address as a word break

A website name can't hold an underscore, so typing My_Cool Site gave
mycool-site. Underscores now become a hyphen, as spaces do; a worker
name keeps them.

* fix(dashboard): keep the letter when an accented one is typed into a publish address

Café became caf: an accented letter isn't a DNS character, so it was
dropped whole. The accent comes off and the letter stays.

* fix(dashboard): cap a website address at a hostname label's 63 characters

The server takes a 64-character subdomain, but DNS caps a label at 63,
so the site it publishes can't be reached. The field now stops at 63,
as the worker one does.

* fix(dashboard): let a publish address finish composing before shaping it

The address was reshaped on every input, composing included: a dead
key's accent turned into a hyphen before its letter arrived, and IME
text was rewritten under the composition. Composed text is now shaped
once it's in.
2026-10-25 21:34:39 -07:00