Skip to content

Commit d835b31

Browse files
rootroot
authored andcommitted
Fix campaign refresh and restore Android home
1 parent 4f58387 commit d835b31

9 files changed

Lines changed: 86 additions & 34 deletions

AndroidManifest.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@
2222
<action android:name="android.intent.action.MAIN" />
2323
<category android:name="android.intent.category.LAUNCHER" />
2424
</intent-filter>
25-
<intent-filter>
26-
<action android:name="android.intent.action.MAIN" />
27-
<category android:name="android.intent.category.HOME" />
28-
<category android:name="android.intent.category.DEFAULT" />
29-
</intent-filter>
3025
</activity>
3126
<service
3227
android:name=".AmbientService"

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ All notable changes to TTRPG Control Deck are recorded here.
66

77
- Renamed the public product, repository, APK, screenshots, and setup package from GM Deck to TTRPG Control Deck.
88
- Preserved the existing Android package, deep-link scheme, storage keys, and backup format for compatibility.
9+
- Refreshed tool views when navigating and isolated dice and saved-library state by campaign.
10+
- Enlarged the header tools into touch-sized icons with an expanded active tool.
11+
- Restored normal Android Home-button behavior by keeping the deck as a regular launcher app instead of a replacement Home app.
912

1013
## 3.1.0 — 2026-08-31
1114

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ An offline-first tabletop companion that turns a small Android display into a fo
2525

2626
TTRPG Control Deck 3.1.0 is designed around the 960×480 Echo Show 5 viewport while remaining usable in a normal Android WebView. Its major features are:
2727

28+
- Runs as a regular Android app, so the device Home button returns to the tablet launcher.
2829
- DM and Player interfaces switchable in two taps.
29-
- Campaign profiles for Foundry VTT, Roll20, D&D 5e, Pathfinder 2e, or mixed tables.
30+
- Campaign profiles for Foundry VTT, Roll20, D&D 5e, Pathfinder 2e, or mixed tables, with campaign-isolated character, combat, session, dice, and saved-library state.
31+
- A compact, touch-sized tool dock whose selected icon expands in place and opens a fully usable tool view without leaving the app.
3032
- Editable Scene Director presets combining mode, destination, background ambience, and timer.
3133
- Persistent character HP, resources, roll modifiers, conditions, notes, and turn status.
3234
- Fast d4–d100 rolling, formulas, modifiers, advantage, disadvantage, history, and repeat roll.
@@ -59,7 +61,7 @@ npx playwright install chromium
5961
npm run test:ui
6062
```
6163

62-
The test opens the app at exactly 960×480, checks DM/Player switching, Player persistence and dice, Scene Director apply/edit/clear behavior, persistent control-strip bounds, and browser errors. It writes four current screenshots to `screenshots/`.
64+
The test opens the app at exactly 960×480 and checks the expanding touch tool dock, screen redraws, campaign state isolation, DM/Player switching, Player persistence and dice, Scene Director apply/edit/clear behavior, persistent control-strip bounds, and browser errors. It writes four current screenshots to `screenshots/`.
6365

6466
Build the Android APK on a compatible Debian/Ubuntu Android SDK environment:
6567

assets/index.html

Lines changed: 34 additions & 27 deletions
Large diffs are not rendered by default.
-4.41 KB
Loading
-2.52 KB
Loading
-4.36 KB
Loading
-4.94 KB
Loading

tests/ui-smoke.mjs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ if (process.env.GMDECK_CHROMIUM) launchOptions.executablePath = process.env.GMDE
1313
const browser = await chromium.launch(launchOptions);
1414
const context = await browser.newContext({ viewport: { width: 960, height: 480 }, deviceScaleFactor: 1 });
1515
const page = await context.newPage();
16+
await page.emulateMedia({ reducedMotion: 'reduce' });
1617
const errors = [];
1718
page.on('pageerror', error => errors.push(error.message));
1819
await page.goto(pathToFileURL(path.join(root, 'assets', 'index.html')).href);
@@ -56,6 +57,16 @@ const assertChromeFits = async label => {
5657

5758
await click('#welcomeDone');
5859
await assertChromeFits('DM view');
60+
const toolDock = await page.evaluate(() => {
61+
const active = document.querySelector('nav button.active').getBoundingClientRect();
62+
const compact = document.querySelector('nav button[data-page="dice"]').getBoundingClientRect();
63+
const icon = document.querySelector('nav button[data-page="dice"] img').getBoundingClientRect();
64+
return { activeWidth: active.width, compactWidth: compact.width, compactHeight: compact.height, iconWidth: icon.width };
65+
});
66+
check(toolDock.compactWidth >= 44 && toolDock.compactHeight >= 44, 'Compact tool buttons must remain usable touch targets');
67+
check(toolDock.iconWidth >= 24, 'Compact tool icons are too small for the target display');
68+
check(toolDock.activeWidth > toolDock.compactWidth, 'The selected tool did not expand in place');
69+
check(await page.locator('nav button.active').getAttribute('aria-current') === 'page', 'Selected tool is not exposed to assistive technology');
5970
await page.screenshot({ path: path.join(screenshotDir, 'ttrpg-control-deck-3.1-dm-view.png') });
6071

6172
await click('#viewSwitch');
@@ -64,6 +75,8 @@ await page.screenshot({ path: path.join(screenshotDir, 'ttrpg-control-deck-3.1-r
6475
await click('[data-view-choice="player"]');
6576
check(await page.locator('#player').isVisible(), 'Player view did not open on the second tap');
6677
check(await page.locator('body').getAttribute('data-view') === 'player', 'Player role was not applied');
78+
const activeToolLabelFits = await page.locator('nav button.active .tool-label').evaluate(element => element.scrollWidth <= element.clientWidth);
79+
check(activeToolLabelFits, 'Expanded tool label is clipped');
6780
check(await page.locator('.player-condition').count() === 8, 'Player view must expose eight quick conditions');
6881
check(await page.locator('[data-role="dm"]:visible').count() === 0, 'DM-only navigation remained visible in Player view');
6982
check(!await page.locator('#sceneControl').isVisible() && !await page.locator('#sessionMode').isVisible(), 'DM-only footer controls remained visible in Player view');
@@ -79,6 +92,10 @@ await click('[data-player-roll="attack"]');
7992
const playerState = await page.evaluate(() => JSON.parse(localStorage.getItem('player:main')));
8093
check(playerState.name === 'Valeros' && playerState.maxHp === 30 && playerState.hp === 29 && playerState.attack === 7, 'Player state did not persist per campaign');
8194
check(/^\d+$/.test(await page.locator('#stripDice').textContent()), 'Player quick roll did not update the persistent dice result');
95+
await click('[data-page="dice"]');
96+
await page.locator('#playerName').evaluate(element => { element.value = 'Stale screen value'; });
97+
await click('[data-page="player"]');
98+
check(await page.locator('#playerName').inputValue() === 'Valeros', 'Revisiting a screen did not refresh it from canonical state');
8299
await page.screenshot({ path: path.join(screenshotDir, 'ttrpg-control-deck-3.1-player-view.png') });
83100

84101
await click('#viewSwitch');
@@ -110,11 +127,39 @@ await page.locator('#sessionMode').dispatchEvent('change');
110127
const activeScene = await page.evaluate(() => JSON.parse(localStorage.getItem('appState')).campaigns.main.activeScene);
111128
check(activeScene === null, 'Manual mode change did not clear the active scene');
112129

130+
await page.evaluate(() => {
131+
appState.campaigns.second = JSON.parse(JSON.stringify(appState.campaigns.main));
132+
appState.campaigns.second.name = 'Second Campaign';
133+
persistApp();
134+
localStorage.setItem('diceHistory:main', JSON.stringify(['MAIN ONLY']));
135+
localStorage.setItem('diceHistory:second', JSON.stringify(['SECOND ONLY']));
136+
localStorage.setItem('srdFavorites:main', JSON.stringify([{ name: 'Main Entry', url: '/main-entry' }]));
137+
localStorage.setItem('srdFavorites:second', JSON.stringify([{ name: 'Second Entry', url: '/second-entry' }]));
138+
localStorage.setItem('lastSrd:main', JSON.stringify({ data: { name: 'Main Entry', index: 'main-entry', url: '/main-entry', desc: ['Main campaign detail'] } }));
139+
localStorage.setItem('lastSrd:second', JSON.stringify({ data: { name: 'Second Entry', index: 'second-entry', url: '/second-entry', desc: ['Second campaign detail'] } }));
140+
});
141+
await click('[data-page="setup"]');
142+
await Promise.all([
143+
page.waitForEvent('load'),
144+
page.locator('#campaignSelect').selectOption('second')
145+
]);
146+
check((await page.locator('#stripCampaign').textContent()).trim() === 'Second Campaign', 'Campaign selection did not refresh the active profile');
147+
await click('[data-page="dice"]');
148+
check((await page.locator('#history').textContent()).includes('SECOND ONLY'), 'Selected campaign dice history did not load');
149+
check(!(await page.locator('#history').textContent()).includes('MAIN ONLY'), 'Dice history leaked between campaigns');
150+
await click('[data-page="reference"]');
151+
check((await page.locator('#srdDetail').textContent()).includes('Second Entry'), 'Selected campaign library detail did not load');
152+
check(!(await page.locator('#srdDetail').textContent()).includes('Main Entry'), 'Library detail leaked between campaigns');
153+
check((await page.locator('#srdResults').textContent()).includes('Second Entry'), 'Selected campaign favourites did not load');
154+
113155
check(errors.length === 0, `Browser errors: ${errors.join('; ')}`);
114156
console.log(JSON.stringify({
115157
viewport: '960x480',
116158
roleSwitchTaps: 2,
117159
playerPersistence: 'passed',
160+
screenRefresh: 'passed',
161+
campaignIsolation: 'passed',
162+
expandableToolDock: 'passed',
118163
playerDice: 'passed',
119164
sceneApplyEditAndClear: 'passed',
120165
screenshots: 4,

0 commit comments

Comments
 (0)