fix: preserve user-renamable Android shortcuts

Chrome Android forces the root URL into PWA installation when required
manifest icon files exist. PWA names come from the manifest and cannot be
edited, which breaks workflows using multiple tracker copies.

Keep required 192/512 icon assets intentionally unavailable by committing
generated files with .png-not suffixes. Removing the manifest link does not
restore root shortcut naming; Chrome still installs the root as a PWA.

Chrome handles /display differently: even with a working manifest it offers
Install app or Create shortcut, and Create shortcut permits renaming. Keep
both manifests intentionally non-installable for consistent user-controlled
shortcut names.

Also add separate player-display favicon/logo artwork and swap /display browser
favicon to player-favicon.ico. Keep generated standard/player logo sources
under renamed inert filenames.
This commit is contained in:
david raistrick
2026-07-15 13:50:53 -04:00
parent 119d252d02
commit dc0a1f1633
12 changed files with 36 additions and 23 deletions
+3 -3
View File
@@ -5,11 +5,11 @@ Backlog of bugs + long-term items. Milestones live in REWORK_PLAN.md.
## Open
caff doent work on fron tpage or big tablet
x caff doent work on fron tpage or big tablet
max/caff need to float or at least be availabe in combagt popout
x max/caff need to float or at least be availabe in combagt popout
save needs bigger save button on char editor
x save needs bigger save button on char editor
x fullscreen and dont lock on main app dm view and the no-game-player view ...and doesnt actually prevent lock on android
+9 -11
View File
@@ -1,27 +1,25 @@
{
"id": "/display",
"short_name": "TTRPG Display",
"name": "TTRPG Initiative Tracker Player Display",
"name": "TTRPG Initiative Tracker - Player Display",
"icons": [
{
"src": "favicon.ico",
"src": "player-favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"src": "player-logo192.png",
"note": "this image intentionally missing from filesystem - keeping manfiest and having broken icon allows android to _shortcut_ which user can control name of.",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/display",
"scope": "/",
"display": "standalone",
"orientation": "landscape",
"scope": "/display",
"note:" "none of the display stuff actually works, even when it properly installs a pwa (which user cant control name of...)",
"display": "fullscreen",
"orientation": "portrait",
"theme_color": "#1A202C",
"background_color": "#1A202C"
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+1 -1
View File
@@ -19,4 +19,4 @@
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+8 -8
View File
@@ -1,4 +1,5 @@
{
"id": "/",
"short_name": "TTRPG Tracker",
"name": "TTRPG Initiative Tracker",
"icons": [
@@ -9,17 +10,16 @@
},
{
"src": "logo192.png",
"note": "this image intentionally missing from filesystem - keeping manfiest and having broken icon allows android to _shortcut_ which user can control name of.",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"start_url": "/",
"scope": "/",
"note:" "none of the display stuff actually works, even when it properly installs a pwa (which user cant control name of...)",
"display": "fullscreen",
"orientation": "portrait",
"theme_color": "#2D3748",
"background_color": "#1A202C"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

+2
View File
@@ -4142,6 +4142,8 @@ function App() {
// swap manifest so Android home-screen installs launch /display
const link = document.querySelector('link[rel="manifest"]');
if (link) link.href = `${process.env.PUBLIC_URL || ''}/display-manifest.json`;
const favicon = document.querySelector('link[rel="icon"]');
if (favicon) favicon.href = `${process.env.PUBLIC_URL || ''}/player-favicon.ico`;
}
if (window.location.pathname === '/logs') {
setIsLogsMode(true);
@@ -39,6 +39,10 @@ function participant(status) {
describe('DisplayView characterization', () => {
beforeEach(() => {
const favicon = document.createElement('link');
favicon.rel = 'icon';
favicon.href = '/favicon.ico';
document.head.appendChild(favicon);
window.history.replaceState({}, '', '/display');
global.alert = jest.fn();
window.open = jest.fn();
@@ -46,9 +50,18 @@ describe('DisplayView characterization', () => {
Element.prototype.scrollIntoView = jest.fn();
});
afterEach(() => {
document.querySelectorAll('link[rel="icon"]').forEach(link => link.remove());
window.history.replaceState({}, '', '/');
});
test('display route swaps browser favicon to player icon', async () => {
seedActiveDisplay();
render(<App />);
await waitFor(() => {
expect(document.querySelector('link[rel="icon"]')).toHaveAttribute('href', '/player-favicon.ico');
});
});
test('DisplayView subscribes via adapter.subscribeDoc (not raw SDK)', async () => {
seedActiveDisplay();
render(<App />);