Skip to content

Commit a232bfc

Browse files
authored
Merge pull request #8 from keyxmakerx/claude/fix-chronicle-entity-loading-8JY9Z
Claude/fix chronicle entity loading 8 jy9 z
2 parents 5242366 + 666ac38 commit a232bfc

3 files changed

Lines changed: 75 additions & 8 deletions

File tree

scripts/sync-dashboard.mjs

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,28 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
109109
// Data
110110
// ---------------------------------------------------------------------------
111111

112+
/**
113+
* Normalize an API response to an array.
114+
* Chronicle endpoints may return a plain array or an object wrapping
115+
* the array under common keys like `data`, `entity_types`, `entities`,
116+
* `maps`, etc. This helper unwraps whichever shape we get.
117+
* @param {*} raw - The raw parsed JSON from the API.
118+
* @param {...string} keys - Object keys to try (in order) if raw is not an array.
119+
* @returns {Array}
120+
* @private
121+
*/
122+
_normalizeArray(raw, ...keys) {
123+
if (Array.isArray(raw)) return raw;
124+
if (raw && typeof raw === 'object') {
125+
for (const key of keys) {
126+
if (Array.isArray(raw[key])) return raw[key];
127+
}
128+
// Last resort: try the generic `data` wrapper.
129+
if (Array.isArray(raw.data)) return raw.data;
130+
}
131+
return [];
132+
}
133+
112134
/** @override */
113135
async _prepareContext(options = {}) {
114136
if (!this._syncManager || !this.api) {
@@ -117,6 +139,7 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
117139

118140
this._loading = true;
119141
const exclusions = this._getExclusions();
142+
const loadErrors = [];
120143

121144
// Build entity tab data.
122145
let entityGroups = [];
@@ -126,6 +149,7 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
126149
foundryOnlyJournals = this._getFoundryOnlyJournals();
127150
} catch (err) {
128151
console.error('Chronicle Dashboard: Failed to load entities', err);
152+
loadErrors.push({ tab: 'entities', message: err.message || 'Failed to load entities' });
129153
}
130154

131155
// Build map tab data.
@@ -134,6 +158,7 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
134158
mapData = await this._buildMapData();
135159
} catch (err) {
136160
console.error('Chronicle Dashboard: Failed to load maps', err);
161+
loadErrors.push({ tab: 'maps', message: err.message || 'Failed to load maps' });
137162
}
138163

139164
// Build shops tab data.
@@ -142,6 +167,7 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
142167
shopData = await this._buildShopData();
143168
} catch (err) {
144169
console.error('Chronicle Dashboard: Failed to load shops', err);
170+
loadErrors.push({ tab: 'shops', message: err.message || 'Failed to load shops' });
145171
}
146172

147173
// Build calendar tab data.
@@ -150,6 +176,7 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
150176
calendarData = await this._buildCalendarData();
151177
} catch (err) {
152178
console.error('Chronicle Dashboard: Failed to load calendar', err);
179+
loadErrors.push({ tab: 'calendar', message: err.message || 'Failed to load calendar' });
153180
}
154181

155182
// Build status tab data.
@@ -168,6 +195,8 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
168195
loading: false,
169196
searchFilter: this._searchFilter,
170197
activeTab: this._activeTab,
198+
loadErrors,
199+
hasLoadErrors: loadErrors.length > 0,
171200

172201
// Config tab.
173202
config: configData,
@@ -208,9 +237,10 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
208237
async _buildEntityGroups(exclusions) {
209238
// Fetch entity types from Chronicle.
210239
if (!this._cache.entityTypes) {
211-
this._cache.entityTypes = await this.api.get('/entity-types');
240+
const raw = await this.api.get('/entity-types');
241+
this._cache.entityTypes = this._normalizeArray(raw, 'entity_types');
212242
}
213-
const types = this._cache.entityTypes || [];
243+
const types = this._cache.entityTypes;
214244

215245
// Fetch all entities (paginated, up to 500 for now).
216246
if (!this._cache.entities) {
@@ -219,8 +249,8 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
219249
let hasMore = true;
220250
while (hasMore && page <= 5) {
221251
const result = await this.api.get(`/entities?per_page=100&page=${page}`);
222-
const entities = result?.entities || result || [];
223-
if (Array.isArray(entities) && entities.length > 0) {
252+
const entities = this._normalizeArray(result, 'entities');
253+
if (entities.length > 0) {
224254
allEntities.push(...entities);
225255
hasMore = entities.length === 100;
226256
page++;
@@ -345,9 +375,10 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
345375
async _buildMapData() {
346376
// Fetch Chronicle maps.
347377
if (!this._cache.maps) {
348-
this._cache.maps = await this.api.get('/maps').catch(() => []);
378+
const raw = await this.api.get('/maps').catch(() => []);
379+
this._cache.maps = this._normalizeArray(raw, 'maps');
349380
}
350-
const chronicles = this._cache.maps || [];
381+
const chronicles = this._cache.maps;
351382

352383
// Index Foundry scenes by linked mapId.
353384
const scenesByMapId = new Map();
@@ -395,9 +426,10 @@ export class SyncDashboard extends HandlebarsApplicationMixin(ApplicationV2) {
395426
async _buildShopData() {
396427
// Ensure entity types are cached.
397428
if (!this._cache.entityTypes) {
398-
this._cache.entityTypes = await this.api.get('/entity-types');
429+
const raw = await this.api.get('/entity-types');
430+
this._cache.entityTypes = this._normalizeArray(raw, 'entity_types');
399431
}
400-
const types = this._cache.entityTypes || [];
432+
const types = this._cache.entityTypes;
401433

402434
// Find the shop entity type by slug or name.
403435
const shopType = types.find(t =>

styles/chronicle-sync.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,27 @@
228228
border-bottom: 1px solid rgba(96, 165, 250, 0.15);
229229
}
230230

231+
/* Load error banner */
232+
.dashboard-load-errors {
233+
display: flex;
234+
align-items: center;
235+
flex-wrap: wrap;
236+
gap: 4px;
237+
padding: 6px 10px;
238+
font-size: 12px;
239+
color: #fca5a5;
240+
background: rgba(248, 113, 113, 0.1);
241+
border-bottom: 1px solid rgba(248, 113, 113, 0.25);
242+
}
243+
244+
.dashboard-load-errors i {
245+
margin-right: 4px;
246+
}
247+
248+
.load-error-item {
249+
font-weight: 600;
250+
}
251+
231252
/* Tab navigation */
232253
.dashboard-tabs {
233254
display: flex;

templates/sync-dashboard.hbs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,20 @@
4343
</a>
4444
</nav>
4545

46+
{{!-- Load error banner --}}
47+
{{#if hasLoadErrors}}
48+
<div class="dashboard-load-errors">
49+
<i class="fa-solid fa-triangle-exclamation"></i>
50+
Some data failed to load from Chronicle:
51+
{{#each loadErrors}}
52+
<span class="load-error-item">{{tab}}{{#unless @last}},{{/unless}}</span>
53+
{{/each}}
54+
<button type="button" data-action="refresh" class="dashboard-btn btn-xs" title="Retry">
55+
<i class="fa-solid fa-arrows-rotate"></i> Retry
56+
</button>
57+
</div>
58+
{{/if}}
59+
4660
{{!-- Tab content --}}
4761
<section class="dashboard-content">
4862

0 commit comments

Comments
 (0)