Skip to content

Commit 63142db

Browse files
committed
feat(dashboard): actor picker for the Sync Capability Inspector
The Capability Inspector silently auto-sampled the first hero actor (often a stray like "Festival 1"), with no way to choose which character to inspect — confusing GMs who never "selected" anything yet saw a name they didn't pick. Add a dropdown of all actors of the synced type (linked ones flagged "✓ synced"). The panel re-samples against the chosen actor. Until a pick is made it still auto-picks (first linked, else first of type) and now says so explicitly in the hint. Selection is per-session instance state, like the existing search filter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LaDDiXB5GTVurEzJwYYopf
1 parent 6c9a491 commit 63142db

3 files changed

Lines changed: 74 additions & 4 deletions

File tree

scripts/sync-dashboard.mjs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
122122
/** @type {string} Current search filter text. */
123123
this._searchFilter = '';
124124

125+
/**
126+
* @type {string|null} Actor id the GM chose to inspect in the Status tab's
127+
* Capability Inspector. Null = auto-pick (first linked, else first of type).
128+
* Per-session instance state (like _searchFilter): survives re-renders,
129+
* resets when the dashboard window is closed.
130+
*/
131+
this._capabilityActorId = null;
132+
125133
/** @type {string} Currently active tab (persisted per-client). */
126134
this._activeTab = getSetting('dashboardActiveTab') || 'entities';
127135

@@ -1026,14 +1034,36 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
10261034
);
10271035
const actorType = actorSync?._actorType || actorSync?._adapter?.actorType || 'character';
10281036

1029-
// Sample a representative actor: prefer a linked one, else the first of the type.
10301037
const ofType = (game.actors?.contents || []).filter((a) => a?.type === actorType);
1031-
const sample = ofType.find((a) => a.getFlag?.(FLAG_SCOPE, 'entityId')) || ofType[0] || null;
1038+
1039+
// Which actor to inspect. If the GM picked one (and it still exists), use it;
1040+
// otherwise auto-pick a representative actor: prefer a Chronicle-linked one,
1041+
// else the first of the type. `autoPicked` drives the UI hint so the GM knows
1042+
// the panel chose for them and can switch with the dropdown.
1043+
const chosen = this._capabilityActorId
1044+
? ofType.find((a) => a.id === this._capabilityActorId)
1045+
: null;
1046+
const autoPicked = !chosen;
1047+
const sample = chosen
1048+
|| ofType.find((a) => a.getFlag?.(FLAG_SCOPE, 'entityId'))
1049+
|| ofType[0]
1050+
|| null;
10321051
if (!sample) {
10331052
this._capabilityReport = null;
1034-
return { available: false, actorType, error: `no "${actorType}" actor to sample` };
1053+
return { available: false, actorType, actors: [], error: `no "${actorType}" actor to sample` };
10351054
}
10361055

1056+
// The full pick-list for the dropdown (linked actors flagged so the GM can
1057+
// tell which heroes already sync to Chronicle).
1058+
const actors = ofType
1059+
.map((a) => ({
1060+
id: a.id,
1061+
name: a.name || '(unnamed)',
1062+
linked: !!a.getFlag?.(FLAG_SCOPE, 'entityId'),
1063+
selected: a.id === sample.id,
1064+
}))
1065+
.sort((x, y) => x.name.localeCompare(y.name));
1066+
10371067
// Fetch the system's declared character fields (same source the adapter uses).
10381068
let fieldDefs = { fields: [] };
10391069
try {
@@ -1050,6 +1080,8 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
10501080
return {
10511081
available: !!report.ok,
10521082
actorType,
1083+
actors,
1084+
autoPicked,
10531085
summary: report.summary,
10541086
source: report.source,
10551087
gaps: this._capabilityGaps(report),
@@ -1263,6 +1295,17 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
12631295
});
12641296
});
12651297

1298+
// --- Status tab: Capability Inspector actor picker ---
1299+
// ApplicationV2 `actions` only delegate `click`, so the `<select>` change is
1300+
// wired here directly. Picking an actor re-renders the panel against it.
1301+
const capActorSelect = el.querySelector('.capability-actor-select');
1302+
if (capActorSelect) {
1303+
capActorSelect.addEventListener('change', (e) => {
1304+
this._capabilityActorId = e.currentTarget.value || null;
1305+
this.render({ force: true });
1306+
});
1307+
}
1308+
12661309
// Map tab handlers are wired via the `open-map-journal` action above;
12671310
// no additional select listeners are needed in Path B.
12681311
}

styles/chronicle-sync.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,25 @@
11661166
font-size: 12px;
11671167
}
11681168

1169+
/* Capability Inspector actor picker (Status tab) */
1170+
.capability-actor-picker {
1171+
display: flex;
1172+
align-items: center;
1173+
gap: 8px;
1174+
margin-bottom: 10px;
1175+
}
1176+
1177+
.capability-actor-picker label {
1178+
font-size: 12px;
1179+
font-weight: 600;
1180+
white-space: nowrap;
1181+
}
1182+
1183+
.capability-actor-picker .config-select {
1184+
flex: 1;
1185+
min-width: 0;
1186+
}
1187+
11691188
.error-value {
11701189
color: #ef4444 !important;
11711190
}

templates/sync-dashboard.hbs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,15 @@
10041004
{{#if capability}}
10051005
<h3 class="section-heading">Sync Capability</h3>
10061006
{{#if capability.available}}
1007-
<p class="config-hint">What a Foundry actor exposes vs what Chronicle currently syncs — sampled from <strong>{{capability.source.actorName}}</strong> (type <code>{{capability.source.actorType}}</code>).</p>
1007+
<p class="config-hint">Compares what a Foundry actor exposes against what Chronicle currently syncs.{{#if capability.autoPicked}} Auto-picked <strong>{{capability.source.actorName}}</strong> (the first <code>{{capability.source.actorType}}</code> actor) — pick a different one below to inspect it.{{else}} Inspecting <strong>{{capability.source.actorName}}</strong> (type <code>{{capability.source.actorType}}</code>).{{/if}}</p>
1008+
<div class="capability-actor-picker">
1009+
<label for="capability-actor-select">Inspect actor:</label>
1010+
<select id="capability-actor-select" class="config-select capability-actor-select">
1011+
{{#each capability.actors}}
1012+
<option value="{{id}}" {{#if selected}}selected{{/if}}>{{name}}{{#if linked}} ✓ synced{{/if}}</option>
1013+
{{/each}}
1014+
</select>
1015+
</div>
10081016
<div class="diagnostics-detail">
10091017
<div class="system-info-row"><span class="system-label">Synced:</span><span class="system-value">{{capability.summary.synced}} / {{capability.summary.totalDeclared}} declared</span></div>
10101018
<div class="system-info-row"><span class="system-label">Declared, unmapped:</span><span class="system-value">{{capability.summary.declaredUnmapped}}</span></div>

0 commit comments

Comments
 (0)