Restore Story Intelligence relationship lines

This commit is contained in:
Nick Beckley 2026-07-29 21:01:07 +00:00
parent 8e7f720141
commit cea45d1278
3 changed files with 22 additions and 47 deletions

View File

@ -1177,9 +1177,9 @@ static void Phase21UEnforcesAliasAgeSemanticAndUiResetRules()
Assert(adminDiagnostics.Contains("Reset my Story Intelligence data", StringComparison.Ordinal), "Admin diagnostics page must show the reset button."); Assert(adminDiagnostics.Contains("Reset my Story Intelligence data", StringComparison.Ordinal), "Admin diagnostics page must show the reset button.");
Assert(developmentReset.Contains("PreservedIllustrationItems", StringComparison.Ordinal), "Admin reset result must report preserved generated images."); Assert(developmentReset.Contains("PreservedIllustrationItems", StringComparison.Ordinal), "Admin reset result must report preserved generated images.");
Assert(!developmentReset.Contains("DELETE FROM dbo.IllustrationLibraryItems", StringComparison.Ordinal), "Admin reset must preserve generated illustration library records."); Assert(!developmentReset.Contains("DELETE FROM dbo.IllustrationLibraryItems", StringComparison.Ordinal), "Admin reset must preserve generated illustration library records.");
Assert(script.Contains("renderPresenceCallout", StringComparison.Ordinal), "Stage should show a presence callout for characters in the active location."); Assert(!script.Contains("renderPresenceCallout", StringComparison.Ordinal), "Stage should not draw a bordered character container.");
Assert(script.Contains("drawConnection(id, cluster, locationPoint, \"presence-cluster\", 54, \"\"", StringComparison.Ordinal), "Presence connector should not show a tiny count label."); Assert(script.Contains("drawConnection(id, cluster, locationPoint, \"presence-cluster\", 54, `${presentCharacters.length} here`", StringComparison.Ordinal), "Presence connector should show the location join label.");
Assert(css.Contains(".story-exp-presence-callout", StringComparison.Ordinal), "Presence callout styling is missing."); Assert(!css.Contains(".story-exp-presence-callout", StringComparison.Ordinal), "Character container styling should be removed.");
Assert(css.Contains("white-space: normal", StringComparison.Ordinal), "Location captions should wrap rather than ellipsise."); Assert(css.Contains("white-space: normal", StringComparison.Ordinal), "Location captions should wrap rather than ellipsise.");
Assert(!script.Contains("renderOverflowSummary", StringComparison.Ordinal), "Side panels must not use hidden '+N more' overflow controls."); Assert(!script.Contains("renderOverflowSummary", StringComparison.Ordinal), "Side panels must not use hidden '+N more' overflow controls.");
Assert(css.Contains("-webkit-line-clamp: 3", StringComparison.Ordinal), "Insight body text should be clamped inside its card."); Assert(css.Contains("-webkit-line-clamp: 3", StringComparison.Ordinal), "Insight body text should be clamped inside its card.");
@ -1214,7 +1214,8 @@ static void Phase21RSceneBrowserAndPanelsAreReplayFriendly()
Assert(script.Contains("Assets are recorded in insights/diagnostics but not rendered as stage nodes.", StringComparison.Ordinal), "Asset data should remain available without drawing asset stage nodes."); Assert(script.Contains("Assets are recorded in insights/diagnostics but not rendered as stage nodes.", StringComparison.Ordinal), "Asset data should remain available without drawing asset stage nodes.");
Assert(script.Contains("width * 0.82 - size / 2", StringComparison.Ordinal), "Location should be a larger right-side anchor once asset nodes are removed."); Assert(script.Contains("width * 0.82 - size / 2", StringComparison.Ordinal), "Location should be a larger right-side anchor once asset nodes are removed.");
Assert(script.Contains("{ x: 0.68, y: 0.26, role: \"context\" }", StringComparison.Ordinal), "Character layout should use more of the stage width."); Assert(script.Contains("{ x: 0.68, y: 0.26, role: \"context\" }", StringComparison.Ordinal), "Character layout should use more of the stage width.");
Assert(script.Contains("presence-cluster", StringComparison.Ordinal), "Presence should be shown as a single location callout instead of many confusing location lines."); Assert(script.Contains("scene.relationships", StringComparison.Ordinal), "Relationship lines should be rendered on the stage.");
Assert(script.Contains("presence-cluster", StringComparison.Ordinal), "Presence should still be shown as a location join.");
Assert(css.Contains(".story-exp-zone--location .story-exp-zone-label {\n left: 80%;", StringComparison.Ordinal), "Location lane label should follow the compact right-side anchor."); Assert(css.Contains(".story-exp-zone--location .story-exp-zone-label {\n left: 80%;", StringComparison.Ordinal), "Location lane label should follow the compact right-side anchor.");
Assert(!css.Contains("grid-template-rows: minmax(218px, 1.35fr) minmax(185px, 1.08fr) auto", StringComparison.Ordinal), "Right rail must not use fixed-height panel tracks."); Assert(!css.Contains("grid-template-rows: minmax(218px, 1.35fr) minmax(185px, 1.08fr) auto", StringComparison.Ordinal), "Right rail must not use fixed-height panel tracks.");
Assert(!css.Contains("grid-template-rows: auto auto minmax(170px, 1fr) minmax(96px, 0.56fr)", StringComparison.Ordinal), "Left rail must not use fixed-height panel tracks."); Assert(!css.Contains("grid-template-rows: auto auto minmax(170px, 1fr) minmax(96px, 0.56fr)", StringComparison.Ordinal), "Left rail must not use fixed-height panel tracks.");

View File

@ -792,23 +792,6 @@ input:focus-visible {
height: 100%; height: 100%;
} }
.story-exp-presence-callout {
position: absolute;
left: var(--presence-left, 28px);
top: var(--presence-top, 58px);
z-index: 34;
width: var(--presence-width, 520px);
height: var(--presence-height, 320px);
border: 1px solid rgba(125, 193, 255, 0.22);
border-radius: 28px;
background:
radial-gradient(circle at 62% 45%, rgba(87, 149, 205, 0.14), transparent 58%),
linear-gradient(135deg, rgba(17, 41, 70, 0.22), rgba(4, 10, 20, 0.06));
box-shadow: inset 0 0 44px rgba(125, 193, 255, 0.08), 0 22px 80px rgba(0, 0, 0, 0.16);
pointer-events: none;
transition: left 850ms cubic-bezier(.16, .84, .28, 1), top 850ms cubic-bezier(.16, .84, .28, 1), width 850ms cubic-bezier(.16, .84, .28, 1), height 850ms cubic-bezier(.16, .84, .28, 1);
}
.story-character { .story-character {
position: absolute; position: absolute;
display: block; display: block;

View File

@ -682,47 +682,38 @@
const activeIds = new Set(); const activeIds = new Set();
const occupiedRects = layoutRects(stageRect); const occupiedRects = layoutRects(stageRect);
scene.relationships
.filter((relationship) => relationship.weight >= 72)
.slice(0, 2)
.forEach((relationship, index) => {
const source = dom.visualStage.querySelector(`[data-character-id="${cssEscape(relationship.sourceId)}"]`);
const target = dom.visualStage.querySelector(`[data-character-id="${cssEscape(relationship.targetId)}"]`);
if (!source || !target) return;
const start = connectionPoint(source, stageRect, target);
const end = connectionPoint(target, stageRect, source);
const obstacles = connectionObstacles(stageRect, source, target);
const id = relationship.id || `relationship-${relationship.sourceId}-${relationship.targetId}`;
activeIds.add(id);
drawn.push(drawConnection(id, start, end, relationshipTone(relationship), relationship.weight, relationship.label || "", index, obstacles, occupiedRects));
});
const location = dom.visualStage.querySelector("[data-location-id]"); const location = dom.visualStage.querySelector("[data-location-id]");
const presentCharacters = scene.characters const presentCharacters = scene.characters
.filter((character) => !character.status || !character.status.toLowerCase().includes("absent")) .filter((character) => !character.status || !character.status.toLowerCase().includes("absent"))
.map((character) => dom.visualStage.querySelector(`[data-character-id="${cssEscape(character.id)}"]`)) .map((character) => dom.visualStage.querySelector(`[data-character-id="${cssEscape(character.id)}"]`))
.filter(Boolean); .filter(Boolean);
const cluster = characterClusterPoint(presentCharacters, stageRect); const cluster = characterClusterPoint(presentCharacters, stageRect);
renderPresenceCallout(presentCharacters, location, stageRect);
if (cluster && location) { if (cluster && location) {
const id = `presence-cluster-${location.dataset.locationId}`; const id = `presence-cluster-${location.dataset.locationId}`;
activeIds.add(id); activeIds.add(id);
const locationPoint = connectionPoint(location, stageRect, { getBoundingClientRect: () => ({ left: stageRect.left + cluster.x, top: stageRect.top + cluster.y, width: 0, height: 0 }) }); const locationPoint = connectionPoint(location, stageRect, { getBoundingClientRect: () => ({ left: stageRect.left + cluster.x, top: stageRect.top + cluster.y, width: 0, height: 0 }) });
drawn.push(drawConnection(id, cluster, locationPoint, "presence-cluster", 54, "", drawn.length, [], occupiedRects)); drawn.push(drawConnection(id, cluster, locationPoint, "presence-cluster", 54, `${presentCharacters.length} here`, drawn.length, [], occupiedRects));
} }
removeInactiveSvgConnections(activeIds); removeInactiveSvgConnections(activeIds);
} }
function renderPresenceCallout(characterNodes, locationNode, stageRect) {
let callout = dom.visualStage.querySelector("[data-presence-callout]");
if (!characterNodes.length || !locationNode) {
callout?.remove();
return;
}
if (!callout) {
callout = document.createElement("div");
callout.className = "story-exp-presence-callout";
callout.dataset.presenceCallout = "true";
dom.visualStage.append(callout);
}
const left = Math.max(14, stageRect.width * 0.018);
const top = Math.max(38, stageRect.height * 0.062);
const right = Math.min(stageRect.width * 0.71, stageRect.width - 330);
const bottom = Math.min(stageRect.height - 62, stageRect.height * 0.78);
callout.style.setProperty("--presence-left", `${Math.round(left)}px`);
callout.style.setProperty("--presence-top", `${Math.round(top)}px`);
callout.style.setProperty("--presence-width", `${Math.round(Math.max(520, right - left))}px`);
callout.style.setProperty("--presence-height", `${Math.round(Math.max(300, bottom - top))}px`);
}
function drawConnection(id, start, end, tone, weight, label, index, obstacles, occupiedRects) { function drawConnection(id, start, end, tone, weight, label, index, obstacles, occupiedRects) {
let group = dom.connections.querySelector(`[data-connection-id="${cssEscape(id)}"]`); let group = dom.connections.querySelector(`[data-connection-id="${cssEscape(id)}"]`);
const isNew = !group; const isNew = !group;