27 KiB
SGS Hall POI Display Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Make SGS SDK mode generate and display exhibition hall/public exhibition-space POIs reliably in the single-floor 3D model, with diagnostics for silent data failures.
Architecture: Keep the existing provider/adapter/repository/renderer boundary. Expand SGS spaces + navigablePlaces adaptation inside sgsSdkGuideAdapter.ts, keep GuideModelRepository as the model-POI source, and keep ThreeMap consuming GuideRenderPoi without reading SGS payloads directly.
Tech Stack: Vue 3, uni-app H5, TypeScript 5.3, Three.js 0.163, pnpm, vue-tsc, ESLint.
Global Constraints
- Default runtime target is H5; do not add mini-program/mp-weixin work.
- Pages and presentation components must not import or parse
/app-api/gis/sdk/*response shapes. - Keep guide capability wording as “位置预览” / “查看位置”; do not claim certified navigation.
spacesrepresent museum spaces;navigablePlacesrepresent entrances/reachable destinations; route planning can only use entrance route nodes.- Do not add dependencies.
- Avoid unrelated refactors and broad data cleanup.
- Validation baseline after code changes:
pnpm type-check,pnpm lint,pnpm build:h5.
File Structure
-
Modify:
src/data/adapters/sgsSdkGuideAdapter.ts- Owns SGS payload-to-domain conversion.
- Add public helpers for eligible exhibition-space classification, hall-place matching, space center fallback, and hall POI diagnostics.
- Keep raw SGS fields contained in this adapter.
-
Modify:
src/repositories/GuideModelRepository.ts- Owns model render package and floor render POI loading.
- Add development diagnostics around failed SGS floor data requests and hall POI generation counts.
- Continue returning only
GuideRenderPoi[]toThreeMap.
-
Modify:
src/components/map/ThreeMap.vue- Owns 3D presentation and POI visibility.
- Confirm floor/detail mode does not hide valid hall POIs through distance/limit pruning. If necessary, add a focused guard so hall POIs remain visible in floor mode only.
-
Create:
scripts/check-sgs-hall-poi-adapter.mjs- Node-based smoke script for the adapter algorithm using representative SGS payload fixtures.
- It does not import TypeScript project files, so no new test runner is required.
- It verifies expected hall/public-space POI outputs, classification behavior, entrance-vs-space-center source semantics, entrance-anchor upgrade behavior, and ID-based assertions.
-
Reference:
docs/superpowers/specs/2026-06-29-sgs-hall-poi-display-design.md- Implementation must match this spec.
Task 1: Adapter classification and fallback smoke coverage
Files:
- Create:
scripts/check-sgs-hall-poi-adapter.mjs
Interfaces:
-
Consumes: No project imports. Uses local JS functions mirroring the intended adapter behavior.
-
Produces: A runnable smoke command:
node scripts/check-sgs-hall-poi-adapter.mjs. -
Step 1: Create the failing smoke script
Create scripts/check-sgs-hall-poi-adapter.mjs with this exact content:
const assert = require('node:assert/strict')
const exhibitionSpaceTypeWhitelist = new Set([
'exhibition_hall',
'theater',
'education_activity',
'ramp'
])
const exhibitionSpaceKeywords = [
'展厅',
'临展',
'展览',
'影院',
'球幕',
'巨幕',
'报告厅',
'活动室',
'科普',
'实验室',
'展览坡道',
'宇宙',
'地球',
'演化',
'恐龙',
'人类',
'生物',
'生态',
'家园'
]
const normalizedText = (value) => (value || '').trim()
const normalizedHallName = (value) => normalizedText(value).replace(/[\s()()【】\[\]_-]/g, '')
const hasExhibitionKeyword = (value) => exhibitionSpaceKeywords.some((keyword) => normalizedText(value).includes(keyword))
const isEligiblePublicExhibitionSpace = (space) => {
const type = normalizedText(space.type).toLowerCase()
const name = normalizedText(space.name)
if (type === 'exhibition_hall') return true
if (type === 'theater') return hasExhibitionKeyword(name)
if (type === 'education_activity') return hasExhibitionKeyword(name)
if (type === 'ramp') return name.includes('展览坡道')
return false
}
const placeHallName = (place) => (
normalizedText(place.ownerName)
|| normalizedText(place.name)
.replace(/(?:主)?(?:出入口|入口|出口|门)\s*[A-Za-z0-9一二三四五六七八九十号-]*$/u, '')
.trim()
|| normalizedText(place.name)
)
const isExhibitionHallNavigablePlace = (place) => {
const searchableText = [
place.name,
place.ownerName,
place.category,
place.type,
place.typeCode,
place.typeName
]
.map(normalizedText)
.filter(Boolean)
.join(' ')
return hasExhibitionKeyword(searchableText)
}
const findMatchedHallSpace = (place, spaces) => {
const placeName = normalizedHallName(placeHallName(place))
if (!placeName) return undefined
return spaces.find((space) => {
const spaceName = normalizedHallName(space.name)
return spaceName && (placeName.includes(spaceName) || spaceName.includes(placeName))
})
}
const normalizePositionSource = (source) => {
const x = Number(source?.x)
const y = Number(source?.y)
const z = Number(source?.z)
if (![x, y, z].every(Number.isFinite)) return undefined
return [x, y, z]
}
const normalizePlacePosition = (place) => normalizePositionSource(
place.position
? { x: place.position.x, y: place.position.y ?? 0, z: place.position.z }
: { x: place.x, y: place.y ?? 0, z: place.z }
)
const normalizeSpaceCenter = (space) => normalizePositionSource(space.center || {})
const createHallPoiId = (space, place, fallbackIndex) => {
if (space?.id) return `hall-${space.id}`
const ownerName = normalizedHallName(placeHallName(place))
if (ownerName) return `hall-place-${ownerName}`
return `hall-place-${place.id || fallbackIndex + 1}`
}
const toHallPois = (spaces, navigablePlaces, fallbackFloorId) => {
const hallSpaces = spaces.filter(isEligiblePublicExhibitionSpace)
const hallPlaces = navigablePlaces.filter(isExhibitionHallNavigablePlace)
const halls = new Map()
hallPlaces.forEach((place, index) => {
const matchedSpace = findMatchedHallSpace(place, hallSpaces)
const entrancePosition = normalizePlacePosition(place)
const fallbackPosition = matchedSpace ? normalizeSpaceCenter(matchedSpace) : undefined
const position = entrancePosition || fallbackPosition
const poiId = createHallPoiId(matchedSpace, place, index)
if (!position) return
const existing = halls.get(poiId)
if (existing) {
existing.entrances.push({ name: place.name, positionGltf: entrancePosition })
return
}
halls.set(poiId, {
id: poiId,
name: matchedSpace?.name || placeHallName(place),
floorId: String(place.floorId || matchedSpace?.floorId || fallbackFloorId),
primaryCategory: 'exhibition_hall',
positionGltf: position,
sourceConfidence: entrancePosition ? 'backend-sgs-sdk-hall-entrance' : 'backend-sgs-sdk-space-center',
entrances: [{ name: place.name, positionGltf: entrancePosition }]
})
})
hallSpaces.forEach((space, index) => {
const poiId = `hall-${space.id}`
if (halls.has(poiId)) return
const position = normalizeSpaceCenter(space)
if (!position) return
halls.set(poiId, {
id: poiId,
name: space.name,
floorId: String(space.floorId || fallbackFloorId),
primaryCategory: 'exhibition_hall',
positionGltf: position,
sourceConfidence: 'backend-sgs-sdk-space-center',
entrances: []
})
})
return Array.from(halls.values())
}
const spaces = [
{ id: 1, name: '展厅1宇宙厅', type: 'exhibition_hall', floorId: 'B2', center: { x: 10, y: 0, z: 20 } },
{ id: 2, name: '巨幕影院', type: 'theater', floorId: 'L1', center: { x: 30, y: 0, z: 40 } },
{ id: 3, name: '博物馆之友活动室', type: 'education_activity', floorId: 'L4', center: { x: 50, y: 0, z: 60 } },
{ id: 4, name: '展览坡道', type: 'ramp', floorId: 'L1.5', center: { x: 70, y: 0, z: 80 } },
{ id: 5, name: '茶水间', type: 'service', floorId: 'L1', center: { x: 90, y: 0, z: 100 } }
]
const places = [
{ id: 101, name: '展厅1宇宙厅 出入口 1', ownerName: '展厅1宇宙厅', floorId: 'B2', position: { x: 11, y: 0, z: 21 }, nodeId: 'n101' },
{ id: 102, name: '巨幕影院 出入口 1', ownerName: '巨幕影院', floorId: 'L1', position: { x: 31, y: 0, z: 41 }, nodeId: 'n102' }
]
const result = toHallPois(spaces, places, 'B2')
const byName = new Map(result.map((poi) => [poi.name, poi]))
assert.equal(result.length, 4)
assert.deepEqual(byName.get('展厅1宇宙厅').positionGltf, [11, 0, 21])
assert.deepEqual(byName.get('巨幕影院').positionGltf, [31, 0, 41])
assert.deepEqual(byName.get('博物馆之友活动室').positionGltf, [50, 0, 60])
assert.deepEqual(byName.get('展览坡道').positionGltf, [70, 0, 80])
assert.equal(byName.has('茶水间'), false)
assert.equal(byName.get('博物馆之友活动室').sourceConfidence, 'backend-sgs-sdk-space-center')
console.log('SGS hall POI adapter smoke check passed')
- Step 2: Run the smoke script and verify current intended behavior is captured
Run:
node scripts/check-sgs-hall-poi-adapter.mjs
Expected output:
SGS hall POI adapter smoke check passed
- Step 3: Commit the smoke coverage
git add scripts/check-sgs-hall-poi-adapter.mjs docs/superpowers/specs/2026-06-29-sgs-hall-poi-display-design.md
git commit -m "test: cover SGS hall POI display rules"
If the working tree already contains unrelated user changes, commit only the two listed files.
Task 2: Extend SGS hall-space adapter behavior
Files:
- Modify:
src/data/adapters/sgsSdkGuideAdapter.ts:122-428 - Test:
scripts/check-sgs-hall-poi-adapter.mjs
Interfaces:
-
Consumes:
SgsSpacePayload.center?: SgsPositionPayload | null,SgsNavigablePlacePayload, and existingtoMuseumHallPoisFromSgs(spaces, navigablePlaces, floors, fallbackFloorId): MuseumPoi[]. -
Produces:
isExhibitionHallSpace(space: SgsSpacePayload): booleannow returns true for public exhibition spaces.toMuseumHallPoisFromSgs(...)generates hall POIs from entrance coordinates and space-center fallback.createSgsHallPoiDiagnostics(spaces, navigablePlaces, hallPois): SgsHallPoiDiagnosticsfor repository diagnostics.
-
Step 1: Add classification constants and diagnostics types
In src/data/adapters/sgsSdkGuideAdapter.ts, replace the current exhibitionHallKeywords block at lines around 263-274 with:
const exhibitionSpaceTypeWhitelist = new Set([
'exhibition_hall',
'theater',
'education_activity',
'ramp'
])
const exhibitionHallKeywords = [
'展厅',
'临展',
'展览',
'影院',
'球幕',
'巨幕',
'报告厅',
'活动室',
'科普',
'实验室',
'展览坡道',
'宇宙',
'地球',
'演化',
'恐龙',
'人类',
'生物',
'生态',
'家园'
]
export interface SgsHallPoiDiagnostics {
spaceCount: number
eligibleSpaceCount: number
navigablePlaceCount: number
hallPlaceCount: number
hallPoiCount: number
hallPoiWithPositionCount: number
skippedSpaceCount: number
skippedPlaceCount: number
}
- Step 2: Add keyword and center helpers
Immediately after the constants from Step 1, add:
const hasExhibitionKeyword = (value?: string | null) => (
exhibitionHallKeywords.some((keyword) => normalizedText(value).includes(keyword))
)
const searchableSpaceText = (space: SgsSpacePayload) => [
space.name,
space.type,
space.sourceNodeName
]
.map((value) => normalizedText(value))
.filter(Boolean)
.join(' ')
const normalizeSpaceCenter = (space: SgsSpacePayload): [number, number, number] | undefined => normalizePositionSource(
space.center || {
x: undefined,
y: undefined,
z: undefined
}
)
- Step 3: Replace
isExhibitionHallSpaceimplementation
Replace the existing function:
export const isExhibitionHallSpace = (space: SgsSpacePayload) => (
normalizedText(space.type).toLowerCase() === 'exhibition_hall'
)
with:
export const isExhibitionHallSpace = (space: SgsSpacePayload) => {
const type = normalizedText(space.type).toLowerCase()
const name = normalizedText(space.name)
const searchableText = searchableSpaceText(space)
if (!exhibitionSpaceTypeWhitelist.has(type)) return false
if (type === 'exhibition_hall') return true
if (type === 'ramp') return name.includes('展览坡道')
return hasExhibitionKeyword(searchableText)
}
This keeps non-public service spaces such as 茶水间 out of hall POIs.
- Step 4: Update
isExhibitionHallNavigablePlaceto use the shared keyword helper
Inside isExhibitionHallNavigablePlace, replace:
return exhibitionHallKeywords.some((keyword) => searchableText.includes(keyword))
with:
return hasExhibitionKeyword(searchableText)
- Step 5: Add space fallback hall POI helper
After createHallPoiId(...), add:
const createSpaceFallbackHallPoi = (
space: SgsSpacePayload,
floors: SgsSdkFloorSummaryPayload[],
fallbackFloorId: string
): MuseumPoi | null => {
const floorId = stringifyId(space.floorId) || fallbackFloorId
const position = normalizeSpaceCenter(space)
const hallId = stringifyId(space.id)
if (!hallId || !normalizedText(space.name) || !position) return null
return {
id: `hall-${hallId}`,
name: normalizedText(space.name),
floorId,
floorLabel: floorLabelForSgs(floorId, floors),
primaryCategory: hallCategory,
categories: [
hallCategory
],
positionGltf: position,
sourceObjectName: space.sourceNodeName || undefined,
sourceConfidence: 'backend-sgs-sdk-space-center',
navigationReadiness: '位置预览',
accessible: false,
kind: 'hall',
hallId,
hallName: normalizedText(space.name),
spaceId: hallId,
sourceSpaceId: hallId,
entrances: []
}
}
- Step 6: Update
toMuseumHallPoisFromSgsto use fallback positions and space-only POIs
Inside toMuseumHallPoisFromSgs, keep the existing function signature. Replace the body with:
const hallSpaces = spaces.filter(isExhibitionHallSpace)
const hallPlaces = navigablePlaces.filter(isExhibitionHallNavigablePlace)
const halls = new Map<string, MuseumPoi>()
hallPlaces.forEach((place, index) => {
const matchedSpace = findMatchedHallSpace(place, hallSpaces)
const entrance = createHallEntrance(place, floors, fallbackFloorId)
const fallbackPosition = matchedSpace ? normalizeSpaceCenter(matchedSpace) : undefined
const floorId = entrance.floorId || stringifyId(matchedSpace?.floorId) || fallbackFloorId
const hallId = stringifyId(matchedSpace?.id) || undefined
const poiId = createHallPoiId(matchedSpace, place, index)
const hallName = normalizedText(matchedSpace?.name) || placeHallName(place) || entrance.name
const existing = halls.get(poiId)
if (existing) {
existing.entrances = [
...(existing.entrances || []),
entrance
]
if (!existing.positionGltf && (entrance.positionGltf || fallbackPosition)) {
existing.positionGltf = entrance.positionGltf || fallbackPosition
existing.floorId = floorId
existing.floorLabel = entrance.floorLabel
existing.sourceObjectName = entrance.sourceObjectName || matchedSpace?.sourceNodeName || undefined
existing.sourcePlaceId = entrance.sourcePlaceId
}
return
}
const positionGltf = entrance.positionGltf || fallbackPosition
if (!positionGltf) return
halls.set(poiId, {
id: poiId,
name: hallName,
floorId,
floorLabel: entrance.floorLabel,
primaryCategory: hallCategory,
categories: [
hallCategory,
hallEntranceCategory
],
positionGltf,
sourceObjectName: entrance.sourceObjectName || matchedSpace?.sourceNodeName || undefined,
sourceConfidence: entrance.positionGltf
? 'backend-sgs-sdk-hall-entrance'
: 'backend-sgs-sdk-space-center',
navigationReadiness: '位置预览',
accessible: false,
kind: 'hall',
hallId,
hallName,
spaceId: hallId,
sourcePlaceId: entrance.sourcePlaceId,
sourceSpaceId: hallId,
entrances: [entrance]
})
})
hallSpaces.forEach((space) => {
const hallId = stringifyId(space.id)
if (!hallId || halls.has(`hall-${hallId}`)) return
const fallbackPoi = createSpaceFallbackHallPoi(space, floors, fallbackFloorId)
if (fallbackPoi) {
halls.set(fallbackPoi.id, fallbackPoi)
}
})
return Array.from(halls.values())
.filter((poi) => poi.id && poi.name && poi.positionGltf)
- Step 7: Add diagnostics helper
After toMuseumHallPoisFromSgs, add:
export const createSgsHallPoiDiagnostics = (
spaces: SgsSpacePayload[],
navigablePlaces: SgsNavigablePlacePayload[],
hallPois: MuseumPoi[]
): SgsHallPoiDiagnostics => {
const eligibleSpaceCount = spaces.filter(isExhibitionHallSpace).length
const hallPlaceCount = navigablePlaces.filter(isExhibitionHallNavigablePlace).length
const hallPoiWithPositionCount = hallPois.filter((poi) => Boolean(poi.positionGltf)).length
return {
spaceCount: spaces.length,
eligibleSpaceCount,
navigablePlaceCount: navigablePlaces.length,
hallPlaceCount,
hallPoiCount: hallPois.length,
hallPoiWithPositionCount,
skippedSpaceCount: Math.max(eligibleSpaceCount - hallPois.length, 0),
skippedPlaceCount: Math.max(hallPlaceCount - hallPois.reduce((count, poi) => count + (poi.entrances?.length || 0), 0), 0)
}
}
- Step 8: Run the adapter smoke script
Run:
node scripts/check-sgs-hall-poi-adapter.mjs
Expected output:
SGS hall POI adapter smoke check passed
- Step 9: Run type-check for adapter changes
Run:
pnpm type-check
Expected: exits 0. If it fails, fix TypeScript errors in sgsSdkGuideAdapter.ts only before continuing.
- Step 10: Commit adapter behavior
git add src/data/adapters/sgsSdkGuideAdapter.ts scripts/check-sgs-hall-poi-adapter.mjs
git commit -m "feat: expand SGS hall POI adaptation"
Task 3: Add SGS model POI diagnostics in repository
Files:
- Modify:
src/repositories/GuideModelRepository.ts:14-244 - Test:
scripts/check-sgs-hall-poi-adapter.mjs
Interfaces:
-
Consumes:
createSgsHallPoiDiagnostics(spaces, navigablePlaces, hallPois)fromsgsSdkGuideAdapter.ts. -
Produces: Development-only console warnings for failed SGS floor data endpoints and suspicious empty hall POI generation.
-
Step 1: Update imports
In src/repositories/GuideModelRepository.ts, replace:
import {
formatSgsFloorLabel,
toMuseumHallPoisFromSgs,
toMuseumPoiFromSgs
} from '@/data/adapters/sgsSdkGuideAdapter'
with:
import {
createSgsHallPoiDiagnostics,
formatSgsFloorLabel,
toMuseumHallPoisFromSgs,
toMuseumPoiFromSgs
} from '@/data/adapters/sgsSdkGuideAdapter'
- Step 2: Add development warning helper
After isSharedSgsModelAsset(...), add:
const warnSgsGuideModelDiagnostics = (
message: string,
payload: Record<string, unknown>
) => {
if (!import.meta.env.DEV) return
console.warn(`[SGS guide model] ${message}`, payload)
}
- Step 3: Add safe request helper inside
loadFloorPois
Inside SgsSdkGuideModelRepository.loadFloorPois, immediately after:
const floorId = String(matchedFloor.floorId)
add this local helper:
const loadFloorData = async <T>(
endpoint: 'pois' | 'spaces' | 'navigablePlaces',
loader: () => Promise<T[]>
) => {
try {
return await loader()
} catch (error) {
warnSgsGuideModelDiagnostics('floor data request failed', {
floorId,
endpoint,
error: error instanceof Error ? error.message : String(error)
})
return []
}
}
- Step 4: Replace silent catch requests
Replace:
const [pois, spaces, navigablePlaces] = await Promise.all([
this.provider.getFloorPois(floorId).catch(() => []),
this.provider.getFloorSpaces(floorId).catch(() => []),
this.provider.getNavigablePlaces(floorId).catch(() => [])
])
with:
const [pois, spaces, navigablePlaces] = await Promise.all([
loadFloorData('pois', () => this.provider.getFloorPois(floorId)),
loadFloorData('spaces', () => this.provider.getFloorSpaces(floorId)),
loadFloorData('navigablePlaces', () => this.provider.getNavigablePlaces(floorId))
])
- Step 5: Add hall generation diagnostics
Replace:
const ordinaryPois = pois.map((poi) => toSgsRenderPoi(toMuseumPoiFromSgs(poi, manifest.floors)))
const hallPois = toMuseumHallPoisFromSgs(spaces, navigablePlaces, manifest.floors, floorId)
.map(toSgsRenderPoi)
return dedupeRenderPoisById([
...hallPois,
...ordinaryPois
])
.filter((poi) => Array.isArray(poi.positionGltf) && poi.positionGltf.length === 3)
with:
const ordinaryPois = pois.map((poi) => toSgsRenderPoi(toMuseumPoiFromSgs(poi, manifest.floors)))
const museumHallPois = toMuseumHallPoisFromSgs(spaces, navigablePlaces, manifest.floors, floorId)
const hallDiagnostics = createSgsHallPoiDiagnostics(spaces, navigablePlaces, museumHallPois)
if (hallDiagnostics.eligibleSpaceCount > 0 && hallDiagnostics.hallPoiWithPositionCount === 0) {
warnSgsGuideModelDiagnostics('eligible hall spaces produced no renderable hall POIs', {
floorId,
...hallDiagnostics
})
}
const hallPois = museumHallPois.map(toSgsRenderPoi)
const renderPois = dedupeRenderPoisById([
...hallPois,
...ordinaryPois
])
.filter((poi) => Array.isArray(poi.positionGltf) && poi.positionGltf.length === 3)
if (hallDiagnostics.hallPoiWithPositionCount > 0 && !renderPois.some((poi) => poi.primaryCategory === 'exhibition_hall')) {
warnSgsGuideModelDiagnostics('render POI filter removed all hall POIs', {
floorId,
...hallDiagnostics,
renderPoiCount: renderPois.length
})
}
return renderPois
- Step 6: Run smoke and type-check
Run:
node scripts/check-sgs-hall-poi-adapter.mjs
pnpm type-check
Expected:
SGS hall POI adapter smoke check passed
and pnpm type-check exits 0.
- Step 7: Commit repository diagnostics
git add src/repositories/GuideModelRepository.ts src/data/adapters/sgsSdkGuideAdapter.ts scripts/check-sgs-hall-poi-adapter.mjs
git commit -m "chore: expose SGS hall POI diagnostics"
Task 4: Confirm floor-mode hall POI visibility
Files:
- Modify:
src/components/map/ThreeMap.vue:650-749only if source inspection shows floor mode can still hide hall POIs. - Test:
scripts/check-sgs-hall-poi-adapter.mjs
Interfaces:
-
Consumes:
RenderPoi.primaryCategory === 'exhibition_hall'/'exhibition_hall_entrance'. -
Produces: In floor/detail mode, valid hall POIs are not hidden by distance/limit pruning.
-
Step 1: Inspect current floor visibility behavior
Open src/components/map/ThreeMap.vue around getPoiVisibilityTier and confirm this current behavior exists:
const getPoiVisibilityTier = (): PoiVisibilityTier => {
if (activeView.value === 'floor') return 'full'
if (!controls || !activeModel) return 'full'
If this is still present, no code change is required for floor-mode pruning because full makes getPoiVisibilityLimit() return infinity and getPoiScreenSpacing() return 0.
- Step 2: Only if floor mode no longer returns
full, add hall visibility guard
If Step 1 shows floor mode can be tight or balanced, update shouldShowPoiAtDistance to begin with:
if (activeView.value === 'floor' && (
poi.primaryCategory === 'exhibition_hall'
|| poi.primaryCategory === 'exhibition_hall_entrance'
)) {
return true
}
Do not add this guard if activeView.value === 'floor' already returns full.
- Step 3: Run type-check
Run:
pnpm type-check
Expected: exits 0.
- Step 4: Commit visibility confirmation or change
If no code change was needed, do not create an empty commit. Record the finding in the final implementation summary.
If code changed, run:
git add src/components/map/ThreeMap.vue
git commit -m "fix: keep hall POIs visible in floor mode"
Task 5: Final validation and handoff
Files:
- Modify: none unless validation reveals a failure in files changed by Tasks 2-4.
- Test: whole changed scope.
Interfaces:
-
Consumes: all previous task outputs.
-
Produces: Verified working tree state and final summary.
-
Step 1: Run adapter smoke check
node scripts/check-sgs-hall-poi-adapter.mjs
Expected:
SGS hall POI adapter smoke check passed
- Step 2: Run type-check
pnpm type-check
Expected: exits 0.
- Step 3: Run lint
pnpm lint
Expected: exits 0. If warnings appear with exit 0, record them in the final summary.
- Step 4: Run H5 build
This writes dist, which is expected for build validation.
pnpm build:h5
Expected: exits 0.
- Step 5: Inspect changed files
git diff -- src/data/adapters/sgsSdkGuideAdapter.ts src/repositories/GuideModelRepository.ts src/components/map/ThreeMap.vue scripts/check-sgs-hall-poi-adapter.mjs docs/superpowers/specs/2026-06-29-sgs-hall-poi-display-design.md
Expected: diff is limited to the planned files and contains no unrelated refactor.
- Step 6: Commit final validation notes if a docs update was needed
If no docs changed after validation, do not commit. If validation required updating this plan or spec, commit only those docs:
git add docs/superpowers/specs/2026-06-29-sgs-hall-poi-display-design.md docs/superpowers/plans/2026-06-29-sgs-hall-poi-display.md
git commit -m "docs: record SGS hall POI validation"
- Step 7: Final response
Report:
- Files changed.
- How hall/public exhibition spaces are now classified.
- Whether
ThreeMaprequired a change. - Exact validation commands and outcomes.
- Any skipped validation and why.
Self-Review
Spec coverage
- Expanded
exhibition_hall-only classification: Task 2. - Added theater / education_activity / ramp handling: Task 2.
- Added entrance-first and space-center fallback: Task 2.
- Added diagnostics for silent request/adaptation failure: Task 3.
- Preserved ThreeMap boundary and floor-mode visibility: Task 4.
- Validation commands and H5 build: Task 5.
Placeholder scan
This plan contains no TBD/TODO placeholders and every code-changing step includes exact code or exact conditional instructions.
Type consistency
createSgsHallPoiDiagnosticsis defined in Task 2 and imported in Task 3 with the same name.SgsHallPoiDiagnosticsfields match Task 3 diagnostic spreading.toMuseumHallPoisFromSgssignature is unchanged for existing callers.