diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 6500bc56..7f69fa76 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,4 +1,5 @@ + # BMAD Method — Project Instructions ## Project Configuration @@ -8,9 +9,9 @@ - **Communication Language**: English - **Document Output Language**: English - **User Skill Level**: intermediate -- **Output Folder**: {project-root}/_bmad-output -- **Planning Artifacts**: {project-root}/_bmad-output/planning-artifacts -- **Implementation Artifacts**: {project-root}/_bmad-output/implementation-artifacts +- **Output Folder**: {project-root}/\_bmad-output +- **Planning Artifacts**: {project-root}/\_bmad-output/planning-artifacts +- **Implementation Artifacts**: {project-root}/\_bmad-output/implementation-artifacts - **Project Knowledge**: {project-root}/docs ## BMAD Runtime Structure @@ -39,20 +40,42 @@ ## Available Agents -| Agent | Persona | Title | Capabilities | -|---|---|---|---| -| bmad-master | BMad Master | BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator | runtime resource management, workflow orchestration, task execution, knowledge custodian | -| analyst | Mary | Business Analyst | market research, competitive analysis, requirements elicitation, domain expertise | -| architect | Winston | Architect | distributed systems, cloud infrastructure, API design, scalable patterns | -| dev | Amelia | Developer Agent | story execution, test-driven development, code implementation | -| pm | John | Product Manager | PRD creation, requirements discovery, stakeholder alignment, user interviews | -| qa | Quinn | QA Engineer | test automation, API testing, E2E testing, coverage analysis | -| quick-flow-solo-dev | Barry | Quick Flow Solo Dev | rapid spec creation, lean implementation, minimum ceremony | -| sm | Bob | Scrum Master | sprint planning, story preparation, agile ceremonies, backlog management | -| tech-writer | Paige | Technical Writer | documentation, Mermaid diagrams, standards compliance, concept explanation | -| ux-designer | Sally | UX Designer | user research, interaction design, UI patterns, experience strategy | +| Agent | Persona | Title | Capabilities | +| ------------------- | ----------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| bmad-master | BMad Master | BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator | runtime resource management, workflow orchestration, task execution, knowledge custodian | +| analyst | Mary | Business Analyst | market research, competitive analysis, requirements elicitation, domain expertise | +| architect | Winston | Architect | distributed systems, cloud infrastructure, API design, scalable patterns | +| dev | Amelia | Developer Agent | story execution, test-driven development, code implementation | +| pm | John | Product Manager | PRD creation, requirements discovery, stakeholder alignment, user interviews | +| qa | Quinn | QA Engineer | test automation, API testing, E2E testing, coverage analysis | +| quick-flow-solo-dev | Barry | Quick Flow Solo Dev | rapid spec creation, lean implementation, minimum ceremony | +| sm | Bob | Scrum Master | sprint planning, story preparation, agile ceremonies, backlog management | +| tech-writer | Paige | Technical Writer | documentation, Mermaid diagrams, standards compliance, concept explanation | +| ux-designer | Sally | UX Designer | user research, interaction design, UI patterns, experience strategy | ## Slash Commands Type `/bmad-` in Copilot Chat to see all available BMAD workflows and agent activators. Agents are also available in the agents dropdown. + +## Project Architecture: Critical Rules for All Agents + +### main.js globals — NEVER use globalThis + +`public/main.js` and all `public/modules/**/*.js` files are **plain ` - - - - +
+ + + + + + + + + - \ No newline at end of file diff --git a/src/modules/webgl-layer-framework.ts b/src/modules/webgl-layer-framework.ts index 1880899c..1b992298 100644 --- a/src/modules/webgl-layer-framework.ts +++ b/src/modules/webgl-layer-framework.ts @@ -91,25 +91,11 @@ export class WebGL2LayerFrameworkClass { private resizeObserver: ResizeObserver | null = null; private rafId: number | null = null; private container: HTMLElement | null = null; - private _fallback = false; - - get hasFallback(): boolean { - return this._fallback; - } init(): boolean { - this._fallback = !detectWebGL2(); - if (this._fallback) return false; - const mapEl = document.getElementById("map"); - if (!mapEl) { - console.warn( - "WebGL2LayerFramework: #map element not found — init() aborted", - ); - return false; - } + if (!mapEl) throw new Error("Map element not found"); - // Wrap #map in a positioned container so the canvas can be a sibling with z-index const container = document.createElement("div"); container.id = "map-container"; container.style.position = "relative"; @@ -117,7 +103,6 @@ export class WebGL2LayerFrameworkClass { container.appendChild(mapEl); this.container = container; - // Canvas: sibling to #map, pointerless, z-index above SVG (AC1) const canvas = document.createElement("canvas"); canvas.id = "terrainCanvas"; canvas.style.position = "absolute"; @@ -125,27 +110,20 @@ export class WebGL2LayerFrameworkClass { canvas.style.pointerEvents = "none"; canvas.setAttribute("aria-hidden", "true"); canvas.style.zIndex = String(getLayerZIndex("terrain")); - canvas.width = container.clientWidth || 960; - canvas.height = container.clientHeight || 540; + canvas.width = mapEl.clientWidth || 960; + canvas.height = mapEl.clientHeight || 540; container.appendChild(canvas); this.canvas = canvas; - // Three.js core objects (AC4) this.renderer = new WebGLRenderer({ canvas, - antialias: false, + antialias: true, alpha: true, }); - this.renderer.setSize(canvas.width, canvas.height); + this.renderer.setPixelRatio(window.devicePixelRatio); + this.renderer.setSize(canvas.width, canvas.height, false); this.scene = new Scene(); - this.camera = new OrthographicCamera( - 0, - canvas.width, - 0, - canvas.height, - -1, - 1, - ); + this.camera = new OrthographicCamera(0, graphWidth, 0, graphHeight, -1, 1); this.subscribeD3Zoom(); @@ -178,7 +156,6 @@ export class WebGL2LayerFrameworkClass { } unregister(id: string): void { - if (this._fallback) return; const layer = this.layers.get(id); if (!layer || !this.scene) return; const scene = this.scene; @@ -190,7 +167,6 @@ export class WebGL2LayerFrameworkClass { } setVisible(id: string, visible: boolean): void { - if (this._fallback) return; const layer = this.layers.get(id); if (!layer) return; layer.group.visible = visible; @@ -200,14 +176,13 @@ export class WebGL2LayerFrameworkClass { } clearLayer(id: string): void { - if (this._fallback) return; const layer = this.layers.get(id); if (!layer) return; layer.group.clear(); + this.requestRender(); } requestRender(): void { - if (this._fallback) return; if (this.rafId !== null) return; this.rafId = requestAnimationFrame(() => { this.rafId = null; @@ -216,13 +191,7 @@ export class WebGL2LayerFrameworkClass { } syncTransform(): void { - if (this._fallback || !this.camera) return; const camera = this.camera; - const viewX = (globalThis as any).viewX ?? 0; - const viewY = (globalThis as any).viewY ?? 0; - const scale = (globalThis as any).scale ?? 1; - const graphWidth = (globalThis as any).graphWidth ?? 960; - const graphHeight = (globalThis as any).graphHeight ?? 540; const bounds = buildCameraBounds( viewX, viewY, @@ -238,39 +207,38 @@ export class WebGL2LayerFrameworkClass { } private subscribeD3Zoom(): void { - // viewbox is a D3 selection global available in the browser; guard for Node test env - if (typeof (globalThis as any).viewbox === "undefined") return; - (globalThis as any).viewbox.on("zoom.webgl", () => this.requestRender()); + // viewbox is declared as a global in src/types/global.ts (exposed by main.js). + // Guard for Node test env where it doesn't exist. + if (typeof viewbox === "undefined") return; + viewbox.on("zoom.webgl", () => this.requestRender()); } private observeResize(): void { if (!this.container || !this.renderer) return; + const mapEl = this.container.querySelector("#map") ?? this.container; this.resizeObserver = new ResizeObserver((entries) => { const { width, height } = entries[0].contentRect; - if (this.renderer && this.canvas) { - this.renderer.setSize(width, height); + if (this.renderer && this.canvas && width > 0 && height > 0) { + // updateStyle=false — CSS inset:0 handles canvas positioning. + this.renderer.setSize(width, height, false); this.requestRender(); } }); - this.resizeObserver.observe(this.container); + this.resizeObserver.observe(mapEl); } private render(): void { - if (this._fallback || !this.renderer || !this.scene || !this.camera) return; - const renderer = this.renderer; - const scene = this.scene; - const camera = this.camera; + if (!this.renderer || !this.scene || !this.camera) return; this.syncTransform(); for (const layer of this.layers.values()) { - if (layer.group.visible) { - layer.config.render(layer.group); - } + if (layer.group.visible) layer.config.render(layer.group); } - renderer.render(scene, camera); + this.renderer.render(this.scene, this.camera); } } declare global { var WebGL2LayerFramework: WebGL2LayerFrameworkClass; } -globalThis.WebGL2LayerFramework = new WebGL2LayerFrameworkClass(); + +window.WebGL2LayerFramework = new WebGL2LayerFrameworkClass();