diff --git a/common/libcommon.a b/common/libcommon.a index b9e76632ce..06840394d6 100644 Binary files a/common/libcommon.a and b/common/libcommon.a differ diff --git a/common/params_keys.h b/common/params_keys.h index fc68da5409..7e6ecc9f28 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -318,6 +318,7 @@ inline static std::unordered_map keys = { {"DeveloperSidebarMetric7", {PERSISTENT, INT, "7", "0", 3}}, {"DeveloperUI", {PERSISTENT, BOOL, "0", "0", 3}}, {"GalaxyDeveloperMode", {PERSISTENT | DONT_LOG, BOOL, "0", "0", 0, SETTINGS_SIMPLE}}, + {"GalaxyDeviceName", {PERSISTENT | DONT_LOG, STRING, "", "", 0}}, {"GalaxyMobileDefault", {PERSISTENT | DONT_LOG, BOOL, "1", "1", 0, SETTINGS_SIMPLE}}, {"DeveloperWidgets", {PERSISTENT, BOOL, "1", "0", 3}}, {"DeviceManagement", {PERSISTENT, BOOL, "1", "0", 1, SETTINGS_SIMPLE}}, diff --git a/common/params_pyx.so b/common/params_pyx.so index 0ae72a30b4..a79ce2b752 100755 Binary files a/common/params_pyx.so and b/common/params_pyx.so differ diff --git a/starpilot/system/the_galaxy/assets/mobile/css/material.css b/starpilot/system/the_galaxy/assets/mobile/css/material.css index da1022f6ad..8a89bb0272 100644 --- a/starpilot/system/the_galaxy/assets/mobile/css/material.css +++ b/starpilot/system/the_galaxy/assets/mobile/css/material.css @@ -1285,6 +1285,12 @@ button.gx-chip:hover { .gx-drawer.open { transform: translateX(0); } +.gx-drawer.open ~ .liquid-glass-nav { + opacity: 0; + pointer-events: none; + transform: translateX(-50%) translateY(12px) translateZ(0); +} + .gx-drawer__header { align-items: center; display: flex; @@ -1845,6 +1851,18 @@ button.gx-chip:hover { color: var(--on-surface); } +.gx-device-picker__editor button:disabled { + cursor: wait; + opacity: 0.55; +} + +.gx-device-picker__error { + color: var(--error); + font-size: var(--fs-xs); + line-height: 1.4; + margin-top: var(--sp-2); +} + .gx-scrim { align-items: center; background: rgba(0, 0, 0, 0.6); @@ -2373,6 +2391,7 @@ button.gx-chip:hover { @media (max-width: 767px) { .liquid-glass-nav { display: flex; } + .gx-drawer { padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom, 0px)); } .gx-back-btn { display: inline-flex; } .gx-appbar__search { flex: 1; min-width: 0; } .gx-status-pill { display: none; } diff --git a/starpilot/system/the_galaxy/assets/mobile/js/components/DevicePicker.js b/starpilot/system/the_galaxy/assets/mobile/js/components/DevicePicker.js index 1aeecbce0a..9a80fe962e 100644 --- a/starpilot/system/the_galaxy/assets/mobile/js/components/DevicePicker.js +++ b/starpilot/system/the_galaxy/assets/mobile/js/components/DevicePicker.js @@ -1,5 +1,4 @@ const SLUG_RE = /^[A-Za-z0-9]{16}$/ -const DEVICE_NAMES_KEY = "galaxy-device-names" const MAX_NAME_LENGTH = 40 export const DevicePicker = { @@ -8,9 +7,10 @@ export const DevicePicker = { return { devices: [], activeSlug: "", - customNames: {}, draftName: "", editingSlug: "", + renameError: "", + saving: false, loading: true, } }, @@ -18,30 +18,10 @@ export const DevicePicker = { hasMultipleDevices() { return this.devices.length > 1 }, }, methods: { - loadCustomNames() { - try { - const saved = JSON.parse(localStorage.getItem(DEVICE_NAMES_KEY) || "{}") - if (saved && typeof saved === "object" && !Array.isArray(saved)) { - this.customNames = Object.fromEntries(Object.entries(saved).filter(([slug, name]) => ( - SLUG_RE.test(slug) && typeof name === "string" && name.trim() - ))) - } - } catch (error) { - this.customNames = {} - } - }, - saveCustomNames() { - try { - localStorage.setItem(DEVICE_NAMES_KEY, JSON.stringify(this.customNames)) - } catch (error) { - // Private browsing can disable localStorage; the current name still works. - } - }, displayName(device, index) { - return this.customNames[device.slug] || device.name || `Comma ${index + 1}` + return device.name || `Comma ${index + 1}` }, async loadDevices() { - this.loadCustomNames() try { const response = await fetch("/_gateway/devices", { cache: "no-store" }) if (!response.ok) return @@ -59,6 +39,7 @@ export const DevicePicker = { startRename(device, index) { this.editingSlug = device.slug this.draftName = this.displayName(device, index) + this.renameError = "" this.$nextTick(() => { const input = this.$refs.deviceNameInput ;(Array.isArray(input) ? input[0] : input)?.focus() @@ -67,18 +48,28 @@ export const DevicePicker = { cancelRename() { this.editingSlug = "" this.draftName = "" + this.renameError = "" }, - saveRename(device) { + async saveRename(device) { if (!device) return const name = this.draftName.trim().slice(0, MAX_NAME_LENGTH) - if (name) this.customNames = { ...this.customNames, [device.slug]: name } - else { - const nextNames = { ...this.customNames } - delete nextNames[device.slug] - this.customNames = nextNames + this.saving = true + this.renameError = "" + try { + const response = await fetch(`/_gateway/devices/${encodeURIComponent(device.slug)}/name`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ name }), + }) + const data = await response.json().catch(() => ({})) + if (!response.ok) throw new Error(data?.error || "Could not save the comma name.") + this.devices = this.devices.map((item) => item.slug === device.slug ? { ...item, name: data.name } : item) + this.cancelRename() + } catch (error) { + this.renameError = error?.message || "Could not save the comma name." + } finally { + this.saving = false } - this.saveCustomNames() - this.cancelRename() }, selectDevice(device) { if (!device?.path || device.slug === this.activeSlug) return @@ -114,9 +105,10 @@ export const DevicePicker = {
- - + +
+
{{ renameError }}
`, diff --git a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py index 534da1e92d..fb0afce7c8 100644 --- a/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py +++ b/starpilot/system/the_galaxy/tests/test_ui_vue_frontend.py @@ -66,13 +66,19 @@ def test_ui_index_wires_vue_and_mount_point(): def test_ui_device_picker_uses_gateway_directory_and_preserves_local_galaxy(): picker = _read("js/components/DevicePicker.js") shell = _read("js/components/AppShell.js") + css = _read("css/material.css") + param_keys = (REPO_ROOT / "common/params_keys.h").read_text(encoding="utf-8") assert 'fetch("/_gateway/devices"' in picker - assert "localStorage" in picker + assert 'method: "PUT"' in picker + assert "/name`" in picker assert "Rename comma" in picker assert "hasMultipleDevices" in picker assert "window.location.assign(device.path)" in picker assert shell.index('') > shell.index('v-for="(links, section) in NAV"') + assert '"GalaxyDeviceName", {PERSISTENT | DONT_LOG, STRING' in param_keys + assert "localStorage" not in picker + assert ".gx-drawer.open ~ .liquid-glass-nav" in css assert "Local Galaxy instances do not have the gateway directory endpoint." in picker