Enter or Space on either button fires a click with no coordinates, so the
menu opened at the top-left corner of the screen, far from the button.
A keyboard press now opens it below the button; a pointer click still
opens it at the pointer.
Every item added to the open folder from elsewhere (another tab, an
upload, a move into it) restyled and re-truncated every row in the
listing, so a burst of adds was quadratic in the folder's size: 100 adds
into a 300-item folder took ~800ms of main-thread work. The added row now
takes the listing's column layout on its own (~130ms for the same burst).
Changing the username, or saving a temporary account under a chosen
name, moves the home folder. The GUI rewrote data-path attributes and the
path globals, but the Files tab kept its current folder, back/forward
history, clipboard and drawn rows on the old path, so sorting, New
Folder, Upload, Up, Back and the breadcrumbs all failed with "This folder
couldn't be opened" until a reload. update_username_in_gui now announces
the change, and the Files tab moves its paths over and redraws.
Dark mode's hover color, #2a2a2a, was 1.05:1 against the #262626 cards
and #252525 sidebar, so hovering a notification or sidebar item and
pressing a touch-menu or drawer item showed next to nothing. #333 sits
as far off them as light mode's hover sits off white, and stays below
the selected item's #3d3d3d.
A self-hosted install has no /marketplace/subscriptions/current route, and
Home treated its 404 like any failed read, leaving "Your Plan" at "--" for
good. A 404 now shows the plan every account starts on. The upgrade badge
and link, which have nothing to reach there, go too, unless the hosted
upgrade dialog is loaded.
The muted and tertiary greys carry real information (form hints, file
dates and sizes, share "via" lines, the sidebar's team name,
notification text and times) but fell short of 4.5:1: 2.56:1 and 3.54:1
on white, 3.18:1 and 3.13:1 on dark cards. They now clear it on the
cards and the sidebar in both themes (light #5f6b7d / #707070, dark
#8b95a5 / #94949c). Disabled buttons keep the fainter grey through their
own token, so they still read as disabled.
The hover label is attached to body at z-index 1000, while the dashboard
window's z-index climbs with every click (focusWindow raises it). Over a
long session the window passed 1000 and the labels rendered behind it,
so hovering the collapsed sidebar showed nothing.
The dialogs' primary buttons, the share modal's Share and the photo
crop's Save were filled with the link color under white text: 4.07:1 in
light mode, 3.08:1 in dark, where hover lightened it further to 2.38:1.
They take their own fill now, #4a67f5 (4.62:1) darkening to #3d5bd9
(5.68:1) on hover in both themes; links keep their color.
The table redraws without the button that was pressed, so focus fell back
to the page and the next Tab started from the top. Focus now moves to the
button that replaces it, as the sort headers already do.
Home and Usage both load the monthly usage and the storage space when the
dashboard opens, whichever tab it opens on, so every open sent
/metering/usage and /df twice. Overlapping loads now share one request.
Nothing is cached: a load after the shared one settles fetches again.
An upgrade from the Usage tab's own button, or a return from the billing
portal, refreshed Home behind it while the Usage meter and plan button
kept the old allowance until the user left the tab and came back. Usage
now handles puter:subscription:changed while it is showing.
Its hover style was the sidebar's only one not limited to devices that
hover, so on a wide touch screen (an iPad in landscape) the button kept
its hover shade after being tapped.
The dark-mode chevron override covered the share modal's recipient
selects but not the two General access ones, which kept the darker
light-theme stroke on the dark card.
The coarse-pointer rule that sets the token field to 16px, as the other
dialog inputs are, came before the field's own 13px rule of the same
specificity, so the 13px won and iOS zoomed the page when the field was
focused. The touch rule now follows it.
Saving a temporary account from the user menu promoted the user in place
without a reload, but Home kept its Save session warning (reopening the
save dialog for a saved account) and Account and Security stayed without
their password and email cards until the page was reloaded. The dashboard
now reacts to account-saved, which fires after the user is updated.
Dark mode gave the close button the card color and then inverted the
whole element, which turned the disc light grey and the cross white:
about 1.4:1. Without the background the invert gives the light-mode
button's mirror, a dark disc with a white cross.
The reduced-motion override for the drawer, its scrim and the menu button
came before the drawer rules it was meant to cancel, and the open
drawer's rule outranked it too, so the drawer still slid and the scrim
still faded. The override now follows those rules and covers the open
state.
The checked checkbox, the active Select button and the Done button were
filled with --primary-color, the desktop's text color rather than an
accent: charcoal by default, and white (on white text and ticks) for
anyone whose desktop theme uses light text. They use --select-color, the
accent the rest of the selection already uses.
getMonthlyUsage() resolves with the error body when the request fails, and
Usage and Home read it as a month with nothing used: $0.00 of $0.00, 0%,
and on Usage an empty table replacing the one already on screen. A body
without a usage report is treated as the failure it is, so the last good
numbers stay and a first load shows the "unavailable" note.
The loading overlay sat at the maximum z-index, so on phones it drew over
the open drawer and its scrim and took their taps while a folder loaded.
It only needs to sit above the rows.
The New menu's create wrapper suppressed item.added for as long as it ran,
which for Web Link includes its URL prompt, so files added to the folder
meanwhile never appeared. It now counts only from the actual create. A
create that fails now says so instead of failing silently.
In select mode, a stylus or mouse tap on a phone or tablet toggled the row
in pointerdown and again in click, leaving it as it was; in a narrow
desktop window a click replaced the selection and the selection bar never
showed. Select mode now toggles in click for every pointer and shows its
bar whenever it is on.
These alerts html-encoded the file name before UIAlert encoded the
message again, so "R&D.txt" read "R&D.txt". "already exists." is
translatable now too.
Selecting files inside a trashed folder offered Restore in the
multi-select menu and the selection bar, which then failed once per item:
only direct children of Trash carry restore metadata. The trashed
multi-select menu also drew two separators in a row.
Dropping files from the computer onto a folder row stopped the native
event before the listing's dragster saw it, leaving its enter state stuck:
later drags didn't highlight the listing, and drops on its background
uploaded nothing.
jQuery UI fires no `out` on drop, so the folder row a selection was
dropped on stayed selected (and went to the Trash with the next Delete),
a sidebar folder stayed highlighted as the current one, and a breadcrumb
kept its drop outline.
Escape, or keeping a name unchanged, hid the rename editor but left its
-active class, and the refresh on returning to the browser tab takes that
class for an open editor: the listing stopped refreshing until the next
navigation.
The single-item menu still offered Cut on trashed items, and Ctrl+C
copied them; pasted elsewhere, they kept their raw name in the Trash, a
uid. Trashed items leave through Restore, which puts the original name
back.
The row menu's Paste Into Folder handed a cut to the desktop's
move_clipboard_items, which finds items by their on-screen elements and
plain-string paths. The Files tab's cut stores {path, uid} objects, so
nothing moved and the clipboard was emptied. The menu now takes a paste
handler, and Paste, Ctrl+V and Paste Into Folder share one.
New Folder, Upload, the New/Paste/Upload menu items and file drops were
only refused in the Trash itself and the Shared view. Inside a trashed
folder they created items without the metadata Restore needs, and at /
every create failed (an upload there threw before its progress window
existed and showed nothing). One check now covers the Trash, folders in
it, the Shared view and /.
Upload in a sidebar or breadcrumb folder's menu opened the shared file
picker, whose handler always uploaded into the folder on screen: files
picked for Pictures while viewing Documents landed in Documents. The menu
now hands its folder to the picker.
The uninstall and add-app modals are aria-modal but had no Tab trap, so
Tab walked into the search box and tiles behind the scrim, where typing
filtered the grid under the modal. Closing the add-app modal, and
confirming an uninstall, dropped focus to the page body.
Tab now cycles inside both cards; closing the add-app modal returns focus
to the add tile, and an uninstall leaves focus on the tile that takes the
removed app's slot.
A tile click whose launch rejected (for example the app lookup failing)
only logged to the console: the icon flourish played, settled, and nothing
opened. It now shows the same alert the add-app modal shows for that
failure.
UIAlert encodes its message, so passing it the already-encoded i18n string
showed entities on screen in languages with an apostrophe (French read
"s'est mal passé").
Uninstall keeps the app's name in the saved order so a re-install returns
it to its place, but a deep-link landing appended the tile at the end of
the grid. The install animation played on the last page and the tile then
jumped back to its saved slot on the next refresh. The landing now places
the tile through reconcileAppOrder like every other load.
Landing on /app/<name> (or choosing an option in the add-app modal) adds
the app's tile before the launch runs, because the launch is what installs
it. When the launch failed — no access, no app token, a thrown error — the
tile stayed for the rest of the session, survived refreshes, and every
click on it failed again.
The launch outcome is now passed to settleDeepLinkLaunch, which takes a
tile it drew back out (and restores a previous uninstall record) when
nothing was launched.
The Apps tab read the saved order, folders and uninstalled-apps list with
`.catch(() => null)`, so a failed read looked like a user who had never
saved any. One flaky request emptied the folders and reset the grid to
the default order, and the next drag or folder edit wrote that loss back
to kv for good.
A failed read now keeps what the session already has (the last
uninstalled-apps list included). Until a read of the order or folders has
succeeded, saves stay in memory instead of overwriting the stored value.
The touch action sheet had no roles and never took focus: it was appended
at the end of the page, so a screen reader announced nothing on open and
focus stayed on the page behind it. It's now a menu of menuitems (with
separators and submenu popups marked), takes focus on open, gives it back
on close, moves between enabled items with the arrow keys and Home/End,
and shows an inset focus ring for keyboard use. Taps draw no ring.
The share dialog encodes row names when it renders them, but these two
labels came from i18n() already HTML-encoded, so a translation containing
an apostrophe or ampersand would show its entity text. They're now taken
unencoded like every other row name.
puter.fs.stat both calls the error callback and rejects. The properties
dialog rendered the error from the callback but dropped the promise, so
opening it on a deleted item or offline logged an unhandled rejection.
With more than one website on an item, disassociating one removed the
focused button and returned early before refocusing the dialog, so focus
fell to the page and the next Tab landed in the dashboard behind the modal.
The dialog takes focus back as soon as the entry is removed.
Drag-selecting the path or UID in the properties dialog and releasing over
the dimmed backdrop closed the dialog: the click lands on the overlay when
press and release are on different elements. The backdrop now closes it
only when the press started there, as the share dialog already does.
Each share and properties dialog bound its document keydown under one fixed
namespace, so closing either instance unbound every instance's Escape.
Double-clicking Share… or Properties in the context menu opens two; after
the top one closed, Escape no longer closed the one left behind. Each dialog
now binds under its own namespace.
* chore(dashboard): add the standard file header where it was missing
* fix(dashboard): rename editor keeps entity-like names; rows follow remote renames
The name editor decoded its already-decoded value a second time, so opening
it on a file such as "R&D.txt" and clicking away renamed it to "R&D.txt".
A rename or update from another tab only patched a row's attributes, while
its open/copy/share handlers kept using the entry they were rendered with;
the row now keeps that entry current and refits its name.
* fix(dashboard): Files selection, preview and Trash/Shared edge cases
- A marquee drag without modifiers now replaces a selection made by click,
Ctrl+A or the arrow keys instead of adding to it.
- With the image preview open, clicking another image switches to it, and
moving onto a non-image closes it instead of keeping the old image up.
- Cut (Ctrl+X, multi-select menu) skips trashed items, which leave the Trash
through Restore; Ctrl+V refuses the Trash and the Shared view like the
Paste menu item does.
- Drops on the Shared sidebar entry or breadcrumb are refused (the drag
snaps back) instead of sending writes to a path that isn't a folder.
- The Trash selection bar shows Delete permanently again after a selection
elsewhere had hidden it.
* fix(dashboard): keep an open rename across refreshes; re-sort renamed rows
A same-folder refresh (an upload or paste finishing, a sort change) rebuilt
every row and silently dropped a rename in progress; the editor now comes
back on the same item with the text and caret the user left.
A renamed row whose new name sorts last stayed where its old name was.
* fix(dashboard): Files columns follow the room the listing has
- Resizing a column past the listing's width clamps it instead of resetting
every column to the defaults; a window too narrow for the saved widths
lays out at the defaults but keeps the saved widths for when it widens.
- Columns and grid names re-fit when the window or sidebar resizes the
listing, not only on the next navigation, and grid names hidden behind
another tab are fitted when the Files tab is shown.
- Names are measured in the font they render in (the compact grid's is
smaller), and a column drag relayouts at most once a frame.
* fix(dashboard): Files keyboard and touch edge cases
- The marquee selects file rows only, not the Shared/Trash place rows Home
shows on narrow screens.
- Row shortcuts stand aside while the touch action sheet is open.
- Breadcrumb long-press opens its menu on iPadOS too.
- A folder rendered before the saved view preferences load uses the
defaults instead of throwing and blocking navigation.
* fix(dashboard): share and properties modals — focus, Escape, link state
Share modal:
- Escape pressed in a window opened over the modal (a verification or
upgrade gate) no longer closes the modal underneath.
- Turning the link on or off for a selection too large to list now shows
the result instead of leaving the control on its old state.
- Opening the link up starts at view access; it no longer reuses a hidden
mode left over from earlier, which could hand out edit access.
- Focus returns to the select the user changed.
Properties modal:
- Takes focus when it opens, keeps Tab inside, gives focus back on close,
and leaves Escape to a window opened over it.
- Shows a loading state, and opening Versions on a folder before its
details load no longer leaves an empty body.
- Disassociate is a real button, ignores repeat clicks while pending,
reports a failure, and keeps the website badge while other sites remain.
- Tabs carry tab/tabpanel roles; transitions respect reduced motion.
* fix(dashboard): account dialogs, avatar saves and usage figures
- A form dialog ignores Escape and backdrop clicks while its request runs,
so the outcome isn't lost behind a closed dialog; the close button still
works.
- A new API token, shown only once, survives a stray click outside the card.
- Copy token works on origins without navigator.clipboard (plain-http
self-hosts) instead of throwing, falling back to a selected field.
- Saving one profile picture and then another before the first finished
can no longer leave the first one's picture or failure on screen.
- The storage host bar is clamped to the room left beside Puter's share and
hides its label when there is no host figure.
- Sub-cent dollar costs read "<$0.01" rather than "$0.00", and the usage
meter reads 99% until the allowance is actually spent.
* fix(dashboard): Apps folder drag, search, keys and loading
- A drag inside an open folder that is cancelled (Escape, blur) or dropped
back in place puts the tiles back in their saved order; the cached panel
markup used to skip the rebuild, and the next drag then saved the
cancelled move too.
- Search no longer matches substrings of generated app ids, so "app" or a
single hex letter stops returning every website shortcut.
- Arrow keys and Enter stay with the notifications panel, an account dialog
or the touch action sheet while one is open over the grid.
- The grid shows after 1.5s even if an icon host never answers.
- Refreshing a grid already on screen keeps the page and skips the fade.
- Rename from a folder's menu focuses the name on touch devices too, and
the folder dialog's label follows renames and counts the apps shown.
- i18n inserts %% values literally, so a name containing $& or $' no longer
comes out mangled.
* fix(dashboard): shell navigation, a11y and notification sync
- Missed notifications replayed on load toast as one burst (a few, then a
summary) instead of one toast each.
- A notification read in another tab while this tab's first listing is in
flight is no longer shown as unread when that listing lands.
- Focus stays in the notifications panel when Mark all read or Retry hides
the button that had it.
- The active sidebar tab carries aria-current, and the account button is
named by the username even while the collapsed sidebar hides it.
- Landing with ?ref= replaces the history entry, so one Back leaves.
- Ctrl/Cmd/Shift-click on a sidebar tab opens it in a new browser tab.
- Recently used keeps the newest list when refreshes overlap, and app names
are URL-encoded in the links it opens.
* fix(dashboard): focus rings, dark-mode contrast and reduced motion
- App tiles show a focus ring, not only the hover enlargement; Home card
headers get an inset ring the card's rounded clip can't cut off; the
Delete account button gets the settings cards' ring.
- Pager arrows are mouse-only (the dots page by keyboard), so invisible
arrows are no longer Tab stops, and an arrow with no page leaves the
accessibility tree.
- Dark mode: the Files toolbar's action icons match the nav arrows and stay
readable on the hover chip, hovered breadcrumbs keep their contrast, and
the shared-item dot's ring follows the row background.
- The row '⋯' button is visible at rest on touch screens.
- A long username or email wraps inside the profile card.
- The touch action sheet, the Files selection bar and the app feedback
modal stop scaling/sliding under reduced motion.
* fix(gui): desktop rows store remote renames raw; column resize stays in bounds
- The desktop's item.renamed/item.updated handlers wrote HTML-escaped names
into data-name, the title and the name editor (and titled windows by an
undefined uid). They store the raw name now, as rename_file does, so the
editor shows the real name now that it no longer decodes a second time.
- Resizing a Files column while saved widths are too wide for the listing
starts from the widths on screen, and a width is held at its column's
minimum, so a drag or auto-fit can't save a negative width.
- Double-clicking a row closes the image preview, which would otherwise
float above the app window it opens.
* fix(puter-js): replay the request after a token_auth_failed reauth
A 401 carrying the legacy `token_auth_failed` code opened the sign-in
prompt and then dropped the re-authenticated token on the floor:
`resolveReauth()` fell through to `return null` instead of returning
`{ action: 'replay' }`, so the original call was never re-issued and
rejected with `Unauthorized` even though the new session was valid.
Return the replay verdict the `reauth_required` branch already returns.
Both replay paths stay one-shot (`ctx.done.add('reauth')`,
`spec._replayed`), and the auth gate rejects before the route handler
runs, so a replay has no side effects.
Closes#3953
* fix(puter-js): sign in once when a replayed XHR is refused again
handle_resp ran the reauth flow on the replay's own 401, so a
token_auth_failed that survived the replay reset the fresh token and
opened a second sign-in before rejecting. Treat a replay's 401 as final,
as the 403 verification-gate branch already does.
---------
Co-authored-by: Daniel Salazar <daniel.salazar@puter.com>
`useCredentialChain: true` in `dynamo.aws`, `providers['aws-polly']` or
`providers['aws-textract']` takes credentials from the default AWS chain
instead of static keys, as `s3.s3Config` already does. One provider is shared
and refreshes temporary credentials itself.
* feat(dashboard): account dialogs as dashboard modals instead of windows
Edit profile, change username, change password, change email, create API
token, and delete account in the Account tab (and change password in the
Security tab) now open as dashboard overlay cards, like the share,
properties, and confirm dialogs, instead of desktop UIWindows. They are
centered cards on desktop and tablet and bottom sheets on phones.
- dashboardOverlay.js: the overlay shell UIDashboardDialog already had
(reveal, Esc/backdrop/close dismissal, focus trap, focus return), pulled
out so the new form dialog shares it. It also pins the overlay to the
visual viewport so iOS's keyboard can't cover a sheet's fields, and
leaves Esc to a modal opened on top of it.
- UIDashboardFormDialog.js: labelled fields, password show/hide, an
aria-live status line that marks and focuses the offending field, a busy
submit button, and a done state.
- postUserProtected.js: the cookie-retry / provider re-sign-in / reauth
flow the four account windows each carried, in one place.
- Deleting an account is one dialog now (warning plus confirmation) rather
than two stacked windows.
- Touch: 44px controls, 16px inputs (no iOS focus zoom), no autofocus
that pops the keyboard mid-animation, form buttons side by side on
phones, safe-area padding dropped while the keyboard is up.
- The replaced windows had no other callers and are removed with their
tests, which are ported to the new modules.
* fix(dashboard): a double-click that opens a dialog no longer dismisses it
The second press of the double-click lands on the freshly mounted backdrop
and closed the dialog straight away. Backdrop presses with a click count
above one no longer count toward dismissal.
* fix(dashboard): a failed form submit keeps focus in the dialog
Locking the fields disabled the focused control, which drops focus to the
page, so after an error Enter no longer retried and screen readers lost
their place. The control that had focus gets it back once the fields
unlock, unless the handler moved focus elsewhere in the dialog.
* fix(dashboard): a form error keeps the field it names in view
The status line scrolled the dialog body to the bottom, which on a short
screen (a phone with its keyboard up) left the field being focused and
marked invalid scrolled off the top. The body now scrolls back up just
far enough to show that field.
* fix(gui): a blocked re-sign-in popup fails instead of waiting forever
openRevalidatePopup polled a popup that was never opened, so when the
browser blocked it the account dialog stayed busy indefinitely. It now
rejects straight away and the dialog says the sign-in didn't finish.
* fix(dashboard): password show/hide reads as one action, not a pressed toggle
The button flipped its label between Show and Hide password and also set
aria-pressed, so assistive tech announced "Hide password, pressed" while
the password was showing. It keeps the action label and drops aria-pressed.
* fix(dashboard): screen readers hear a form dialog's success message
The done state arrives as a new status region that already holds its text,
which screen readers don't announce, so after a change succeeded they only
heard "Done, button". The Done button that takes focus is now described by
the message.
* fix(dashboard): requesting an email change no longer sets the cached email
The server only stages the new address until the emailed link is
followed, but the dialog wrote it into window.user.email straight away.
That cached a wrong address for the session and kept refresh_user_data from
updating the Account tab once the change was confirmed, since the two
already matched.
* fix(dashboard): form dialog labels drop the global label shadow
style.css's bare `label` rule gave the field labels a white text-shadow,
which ghosted them on the dark theme, and an extra 3px under each label.
* fix(dashboard): dialog inputs keep a 1px border when focused
style.css's global input :focus rule still set a 2px border width, so a
dialog field's text shifted by a pixel each time it took focus. The focus
ring is the box-shadow; the border stays 1px.
* fix(dashboard): phone dialog inputs stay 16px instead of jumping on focus
On phones style.css's .device-phone input rule outranked the dialog's
16px size at rest, so fields drew at 17px and shrank to 16px the moment
they took focus.
* fix(dashboard): temporary accounts can delete themselves again
The server reports oidc_only for every passwordless account, temporary
ones included, so the shared user-protected helper sent a temporary
account into the linked-provider sign-in, which has no URL for it, and the
deletion never went out. Temporary accounts post directly, as the route
admits them without credentials.