Compare commits

..
Author SHA1 Message Date
Nariman JelvehandClaude Opus 5.5 e43726ef20 refactor: keep folder sort and layout in puter.kv, drop /set_sort_by and /set_layout
Explorer saved each folder's sort and layout through POST /set_sort_by and
/set_layout, which wrote them onto the folder's fsentries row. stat only
returns `layout`, so a saved sort was never read back.

The GUI now keeps them in the user's key-value store, one key per folder
uid (`folder_view:<uid>` holding `{ sort_by, sort_order, layout }`), via
helpers/folderViewPrefs.js. A change to one preference keeps the others.
A folder with nothing stored falls back to the layout its fsentry still
carries, then to the defaults, so existing layouts carry over.

refresh_item_container reads the folder's view after stat and re-sorts
items that went in under the previous sort; a view for a folder the
window has left is ignored. update_window_path no longer applies sort and
layout itself, since the refresh it starts does.

With no callers left, DesktopController and its two routes are removed.
The fsentries columns and stat's `layout` field stay.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-13 21:28:16 -07:00
6 changed files with 10 additions and 39 deletions

No files matched your search

+1 -3
View File
@@ -410,13 +410,11 @@ export const showFramedUpgrade = (win, attribution, tell) => showFramedDialog(wi
* the user bought credit, once the dialog closes.
*
* @param {Window} win
* @param {{ appUid?: string }} attribution - the app whose page frames it; an empty one is passed as null
* @param {(state: 'ready') => void} tell
* @returns {Promise<boolean>}
*/
export const showFramedTopUp = (win, attribution, tell) => showFramedDialog(win, (closed) => {
export const showFramedTopUp = (win, tell) => showFramedDialog(win, (closed) => {
const dialog = new win.UITopUp({
attribution: Object.keys(attribution).length ? attribution : null,
onClose: (result) => closed(result?.toppedUp === true),
});
return dialog.open();
+5 -15
View File
@@ -443,27 +443,17 @@ describe('showFramedTopUp', () => {
let options;
const result = showFramedTopUp(topUpWindow(async (o) => {
options = o;
}), { appUid: 'app-1' }, tell);
}), tell);
await vi.waitFor(() => expect(tell).toHaveBeenCalledWith('ready'));
expect(options.attribution).toEqual({ appUid: 'app-1' });
expect(tell).toHaveBeenCalledTimes(1);
options.onClose({ toppedUp: true });
await expect(result).resolves.toBe(true);
});
it('passes no attribution when the framing page resolves to no app', async () => {
let options;
await showFramedTopUp(topUpWindow(async (o) => {
options = o;
o.onClose();
}), {}, vi.fn());
expect(options.attribution).toBeNull();
});
it('resolves false for a close without a purchase', async () => {
await expect(showFramedTopUp(topUpWindow(async (o) => o.onClose({ toppedUp: false })), {}, vi.fn())).resolves.toBe(false);
await expect(showFramedTopUp(topUpWindow(async (o) => o.onClose()), {}, vi.fn())).resolves.toBe(false);
await expect(showFramedTopUp(topUpWindow(async (o) => o.onClose({ toppedUp: false })), vi.fn())).resolves.toBe(false);
await expect(showFramedTopUp(topUpWindow(async (o) => o.onClose()), vi.fn())).resolves.toBe(false);
});
it('resolves false once the dialog is gone without reporting a close', async () => {
@@ -472,7 +462,7 @@ describe('showFramedTopUp', () => {
root.setAttribute('aria-modal', 'true');
const result = showFramedTopUp(topUpWindow(async () => {
document.body.append(root);
}), {}, vi.fn());
}), vi.fn());
await vi.advanceTimersByTimeAsync(0);
root.remove();
await vi.advanceTimersByTimeAsync(600);
@@ -482,7 +472,7 @@ describe('showFramedTopUp', () => {
it('rejects when the dialog fails to open', async () => {
await expect(showFramedTopUp(topUpWindow(async () => {
throw new Error('no dialog');
}), {}, vi.fn())).rejects.toThrow('no dialog');
}), vi.fn())).rejects.toThrow('no dialog');
});
});
+2 -7
View File
@@ -1089,17 +1089,12 @@ const postAuthActions = async (action) => {
// -------------------------------------------------------------------------------------
// Action: top-up — the deployment's top-up dialog for the first-party page framing it,
// which hears whether the user bought credit once the dialog closes. The app it credits
// is the one the framing page's origin resolves to.
// which hears whether the user bought credit once the dialog closes.
// -------------------------------------------------------------------------------------
if ( action === 'top-up' && window.embedded_in_frame ) {
let toppedUp;
try {
toppedUp = await showFramedTopUp(
window,
window.host_app_uid ? { appUid: window.host_app_uid } : {},
window.tell_popup_frame,
);
toppedUp = await showFramedTopUp(window, window.tell_popup_frame);
} catch (e) {
// A dialog that failed to open leaves the page to open the top-up in a tab.
console.error('top-up action failed', e);
+1 -1
View File
@@ -1889,7 +1889,7 @@ export class UIModule extends EventListener {
onMessage: () => {},
});
if ( framed ) return framed;
await this.#openUpgradeTab(topUpUrl(puter.defaultGUIOrigin, appIDOf(puter)), 'This website wants to open the Puter top-up page.');
await this.#openUpgradeTab(topUpUrl(puter.defaultGUIOrigin), 'This website wants to open the Puter top-up page.');
return { toppedUp: false };
})();
this.#topUpFrame = showing;
@@ -186,16 +186,6 @@ describe('ui.showTopUp_ on a signed-in first-party page', () => {
});
describe('ui.showTopUp_ elsewhere on the web', () => {
it('names the app the page runs as, so the dashboard can credit it', async () => {
fakePuter({ guiOrigin: 'https://puter.com' });
globalThis.puter.appID = 'app-0b8a6d4e-6c1f-4a52-9d3e-2f7b1c9e8a10';
await makeUI().showTopUp_();
expect(window.open).toHaveBeenCalledWith(
'https://puter.com/dashboard/?topup=&topup_app=app-0b8a6d4e-6c1f-4a52-9d3e-2f7b1c9e8a10#usage',
'_blank',
);
});
it.each([
['a page outside the Puter origin', { guiOrigin: 'https://puter.com' }],
['a signed-out first-party page', { signedIn: false }],
+1 -3
View File
@@ -32,13 +32,11 @@ export const upgradeUrl = ({ reason, appID, method } = {}) => {
* The dashboard page that opens the top-up flow, for buying add-on credit.
*
* @param {string} [guiOrigin] The deployment whose dashboard it is; Puter's by default.
* @param {string} [appID] The app whose page asked for the top-up.
* @returns {string}
*/
export const topUpUrl = (guiOrigin, appID) => {
export const topUpUrl = (guiOrigin) => {
const url = new URL('/dashboard/', guiOrigin || UPGRADE_PAGE);
url.searchParams.set('topup', '');
if ( appID ) url.searchParams.set('topup_app', appID);
url.hash = 'usage';
return url.href;
};