/** * Явные связи «символ продуктового кода → browser-smoke» (#241). * * Зачем реестр вообще нужен. Смоки не импортируют исходники: они грузят * собранный бандл и работают через DOM и приватные поля в `page.evaluate`. * Поэтому граф импортов их не свяжет, а совпадение идентификаторов находит * только те смоки, которые называют затронутый символ своим именем. Смок, * проверяющий тот же контракт через другое поле, не найдётся никаким поиском — * и ровно на таком смоке #234 потерял регресс. * * Что сюда попадает. Связь, которую нельзя доказать текстом: смок проверяет * следствие контракта, не называя ни одного изменённого символа. Каждая запись * обязана объяснять, ЧТО именно проверяет смок — иначе реестр превращается в * список суеверий, который никто не решается почистить. * * Чего сюда писать НЕ надо. Связи, которые и так находятся по имени: запись * рядом с прямым совпадением делает вывод selector'а неотличимым от «нашли * потому что записали», и тест начинает проходить по неверной причине. */ /** * @typedef {object} SmokeLink * @property {string[]} symbols Изменённые символы, включающие связь. * @property {string[]} smokes Файлы в `demo/`, без пути. * @property {string} because Что смок проверяет и почему поиском не найдётся. */ /** @type {SmokeLink[]} */ export const SMOKE_LINKS = [ { symbols: ['memoIsoLightFloorRooms', 'IsoFirstFrameState', 'isoPaperContext'], smokes: ['smoke_iso_first_frame.mjs'], because: '#654: the production-bundle witness delays and rejects the lazy 2.5D chunk, then ' + 'samples every animation frame in normal, kiosk and Flat views; no pure import can prove ' + 'that the loading veil hides the transient Flat render or that the first visible marker ' + 'already uses the settled paper-dependent floor class', }, { symbols: ['baseStyles', '_containerOwnedHeight', 'measuredCardHeaderHeight', 'settleSoftStageLayout'], smokes: ['smoke_sections_resize.mjs'], because: '#648: the production-bundle witness measures the HA Sections slot, card, header ' + 'and stage through repeated row resizes and every editor transition; the CSS height chain ' + 'and helper boolean do not otherwise share a runtime symbol with those DOM measurements', }, { symbols: ['chromeStyles', 'dialogsStyles'], smokes: ['smoke_toolbar_stable_width.mjs', 'smoke_editor_tabs.mjs', 'smoke_mobile_view_header.mjs'], because: '#647: the header X slot (.editor-close-slot) and the removed .head .count rule are ' + 'CSS in these sheets; the smokes measure the rendered header width, tab offsets, the X ' + 'hit target and its visibility at 1400/1000/768/390 px — no TS symbol names that layout; ' + '#616: the phone one-row header rules live in chromeStyles too', }, { symbols: ['HeaderMenu', 'headerMenuItems', 'renderHeaderActions'], smokes: ['smoke_mobile_view_header.mjs', 'smoke_support_feedback.mjs', 'smoke_gear_tabs.mjs'], because: '#616: the one-row phone header (<= 480 px) and its gear menu are a media query plus ' + 'these symbols; the smokes tap the real gear and items at 320/390 px and prove >480 px is unchanged', }, { symbols: ['confirmIcon', 'HpConfirmRequest'], smokes: ['smoke_discard_copy.mjs', 'smoke_dialog_polish_603.mjs'], because: '#610: the production-bundle witnesses open all four unsaved-settings ' + 'confirmations, verify both rendered glyphs and actions, then prove an unrelated warning ' + 'still uses the fallback lock glyphs; source imports cannot prove the rendered dialog', }, { symbols: ['rangeLine', 'committedRangeLineValue'], smokes: ['smoke_range_line_draft.mjs', 'smoke_dialog_config_parity.mjs'], because: '#608: the production-bundle witnesses type partial numeric text into real settings ' + 'dialogs, observe that host drafts stay unchanged until commit, and verify field/slider ' + 'synchronisation; neither browser smoke imports the form-kit helper', }, { symbols: ['rangeLine', 'unitInput'], smokes: ['smoke_room_settings_form.mjs', 'smoke_device_settings_form.mjs', 'smoke_space_settings_form.mjs'], because: '#639: the three settings-form smokes drive range-line numbers (room name/label scale, ' + 'device brightness/size/angle, space card font) through the DOM and assert the host draft; ' + 'they never name the form-kit helper, so #608 changed the commit event and only the nightly ' + 'registry saw them turn red', }, { symbols: ['canonicalFurnitureId'], smokes: ['smoke_furniture.mjs'], because: '#606: the bundled furniture palette and saved cactus object are checked through ' + 'the browser DOM; the smoke cannot import the read-only alias resolver by name', }, { symbols: ['formKitCss', 'renderRoomSettingsDialog'], smokes: ['smoke_dialog_polish_603.mjs'], because: '#603: the bundled form-kit track/knob box model, localized discard confirmation ' + 'and headingless room card are measured in a browser; source imports cannot prove ' + 'the rendered geometry or that both actions fit the real modal footer', }, { symbols: ['displayIsNeutral', 'displayWantsValue'], smokes: ['smoke_static_icon.mjs', 'smoke_device_preview_parity.mjs'], because: '#588: два предиката режима наблюдаются как следствия — текст значения внутри ' + 'маркера, отсутствие классов состояния и пульсации, подавленный живой слой пылесоса, ' + 'поля и предупреждения диалога устройства. Ни один смок не называет предикаты: они ' + 'живут внутри бандла, а смок работает через DOM', }, { symbols: ['roomCustomFillOf'], smokes: ['smoke_space_settings.mjs', 'smoke_room_settings.mjs'], because: '#581: the room-colour projection (own custom mode carries the colour, an orphan paints ' + 'the space) is observed through `.room` style variables in View and through the room-dialog ' + 'draft; neither smoke calls the pure function by name', }, { symbols: ['renderPaperShapes'], smokes: [ 'smoke_daycycle_layer_budget.mjs', 'smoke_daycycle_raster.mjs', 'smoke_live_pan_coverage.mjs', 'smoke_bg_color.mjs', 'smoke_smooth_zoom.mjs', ], because: '#582: production-bundle witnesses prove that camera movement activates the ' + 'stage-bounded day-cycle silhouette for direct and animated camera paths at 1 cm/point, ' + 'keeps the #532 raster budget and ' + 'follows the same #579 live viewport as the visible plan; source imports cannot expose ' + 'CDP layers', }, { symbols: [ 'commitHouseplanViewport', 'isIdentityLiveLayerProjection', 'paintLiveViewport', ], smokes: ['smoke_live_pan_coverage.mjs'], because: '#579: the production-bundle witness observes promotion through budgeted viewBox ' + 'refreshes and active Lit commits via scene style attributes; it intentionally does not ' + 'call the viewport helpers whose compositor lifecycle it guards', }, { symbols: ['_renderDevice', '_renderOpenings'], smokes: ['smoke_space_switch_transitions.mjs'], because: '#525: the smoke never names these renderers — it switches spaces the ordinary way ' + 'and reads running transitions off the nodes they produced. The contract it guards is ' + 'list identity: a keyless list reuses a node by position, and the leaf of the next space ' + 'then animates from the previous door\'s angle. Any edit to either renderer can drop the ' + 'key without a single identifier of the smoke changing', }, { symbols: ['LoadedSummaryPanelRuntime', 'SummaryPanelPresentation', 'renderSummaryPanelEditor', 'summaryPanelCss', 'summaryPanelEditorCss', 'summaryPanelDialogCss', 'summaryIcon'], smokes: ['smoke_summary_panel_polish.mjs'], because: 'the #505 browser witness samples retained exit DOM and actual opacity/translation, ' + 'reversal and lifecycle boundaries, then measures the native modal shell and drives ' + 'dependent draft toggles without rewriting unrelated per-card scale preferences', }, { symbols: [ 'confirmedSummaryPanelWriteRecovery', 'summaryPanelEntityIds', 'adoptAuthoritativeGated', 'LoadedSummaryPanelRuntime', ], smokes: ['smoke_summary_panel.mjs'], because: 'the #490 production-bundle sequence proves that a lost config/set ACK adopts the ' + 'whole authoritative document and revision before a second write, and that a summary-only ' + 'HA state row drives the real panel without rendering unrelated ticks or rebuilding geometry', }, { symbols: [ 'roomTempRangeOf', 'roomTempRangeFromDraft', 'roomTempThresholdDraft', 'applyRoomTempThresholdDraft', 'renderRoomSettingsDialog', ], smokes: ['smoke_room_temperature_thresholds.mjs', 'smoke_room_settings_form.mjs'], because: 'the #487 production-bundle scenario proves partial inheritance, zero/invalid input, ' + 'hidden draft retention, reset and narrow-dialog overflow through the real room editor', }, { symbols: [ 'PdfExportRuntime', 'buildPdfPage', 'writePdf', 'stableDimensionEdges', 'pdfSegmentTouchesGeometry', 'SegmentIntersection', 'segmentIntersection', 'COORDINATE_EPSILON', 'PdfCollisionPoint', 'forEachRingSegment', 'pointOnSegment', 'segmentParameter', '_openPdfDialog', '_renderPdfDialog', ], smokes: ['smoke_pdf_export.mjs'], because: 'the #53 production-bundle scenario proves the administrator button, cold-View ' + 'lazy boundary, real modal, remembered options, browser download and parseable A4 output; ' + 'its #484 stepped-facade pass additionally proves the complete external dimension chain ' + 'through the shipped collision helper; those browser and network effects are outside ' + 'the pure PDF scene and writer tests', }, { symbols: [ 'DEFAULT_LABELS', '_closePicker', '_commitHex', '_confirm', '_hexInvalid', '_hexNeedsValidInput', '_surface', ], smokes: [ 'smoke_color_picker.mjs', 'smoke_color_picker_consumers.mjs', 'smoke_help_affordance.mjs', ], because: 'the #476 production-bundle scenarios prove the full-width localized confirmation, ' + 'live-event count, invalid-HEX latch, focus and click containment in both the native ' + 'popover and the picker surface inside a general-settings dialog; those browser ' + 'lifecycle effects are not visible to source or pure color conversion tests', }, { symbols: ['HpDialog', '_ensureNativeModal', '_useHaDialog', '_usesHaDialog', 'dialogsStyles'], smokes: ['smoke_dialog_modal_recovery.mjs', 'smoke_ha_form_shell_parity.mjs'], because: 'the #463 production-bundle scenario observes native top-layer state, geometric ' + 'centring, ::backdrop, update/reconnect recovery and late ha-dialog registration; none ' + 'of those browser lifecycle effects can be proved from the Lit source alone. The #609 ' + 'scenario separately exercises the public HA sizing tokens, one HA body scroller, form ' + 'canvas and responsive fullscreen geometry used by all five form-shell consumers', }, { symbols: [ 'commitDraftSegmentGeometry', 'draftLiveCandidateSpace', 'draftLiveSeed', 'isSingleDraftAppend', 'DraftLiveProjection', 'DraftLiveCommitRuntime', ], smokes: ['smoke_wall_draw_click.mjs'], because: 'the #461 production-bundle scenario proves local rejection rollback and exact ' + 'Undo/Redo identity through the real Walls click handler; those browser effects are ' + 'outside the pure projector and transaction source contracts', }, { symbols: [ 'commitWallChainFinishGeometry', 'finalizeWallChainSpace', '_finalizeWallChainPartitions', '_canonicalWallChainHistoryState', 'rewriteMarkerRoomReferences', 'restoreMarkerRoomReferences', ], smokes: ['smoke_writer_fixed_point.mjs'], because: 'the #477 production-bundle scenario proves that a finished chain and its later ' + 'Undo/Redo states remain Optimize no-ops, and that Delete room updates and restores ' + 'direct and cross-space vacuum references through the real editor history transaction', }, { symbols: [ 'VersionRecoveryController', 'VersionReloadSafetySnapshot', 'isVersionReloadSafe', 'compareRuntimeVersions', 'normalizeRuntimeVersion', 'fetchAuthoritativeConfig', 'getAuthoritativeCardConfig', '_getAuthoritativeConfig', 'adoptCardConfigCapabilities', 'cardVersionReloadSafetySnapshot', '_versionReloadSafetySnapshot', 'renderVersionBanner', '_renderVersionBanner', ], smokes: ['smoke_version_recovery.mjs'], because: 'the #462 production-bundle scenario proves the card-level overlay, trusted ' + 'keyboard/touch reload seam, exact session target, concrete card dialog/write/gesture ' + 'guards, config capability adoption before sibling failures, more-info pause and the ' + 'full-card/space-card boundary; pure controller tests ' + 'cannot observe those DOM and Home Assistant lifecycle effects', }, { symbols: [ 'LiveEditorState', 'finishRevision', 'hoverProperties', 'routeHouseplanEditorUpdate', 'whenHouseplanEditorSettled', ], smokes: ['smoke_furniture.mjs', 'smoke_decor.mjs', 'smoke_decor_text.mjs'], because: 'the #460 production-bundle scenarios consume the internal settlement contract ' + 'through the lazy editor runtime and prove that furniture hover cleanup, preview/commit ' + 'parity and decor gestures observe the latest coalesced live-editor frame', }, { symbols: [ 'renderZigbeeTopologyOverlay', 'resolveTopologyHover', 'mapTopologyNodes', 'readZhaTopology', 'refreshZ2mTopology', 'zigbeeTopologySettingsOf', ], smokes: ['smoke_zigbee_topology_hover.mjs'], because: 'the #54 production-bundle scenario proves default-off lazy loading, explicit provider ' + 'refresh, incident-only mouse hover, cross-space counting, pointer transparency and immediate ' + 'cleanup across leave, touch, editor and non-admin boundaries', }, { symbols: [ 'beginVacuumRouteDraft', 'chooseVacuumRouteSpace', 'commitVacuumRouteDraft', 'VacuumRouteDraft', 'VacuumMapsHost', 'VacuumMapsCardHost', ], smokes: ['smoke_vacuum_route_draft.mjs'], because: 'the #441 production-bundle scenario drives both add-map controls, first-route ' + 'defaulting, required floor selection, cancel, a rejected config write with rollback and ' + 'a successful retry; those UI and transport effects are outside the pure draft helpers', }, { symbols: ['showRoomTooltipOf'], smokes: ['smoke_room_tooltip_toggle.mjs'], because: 'the production-bundle scenario proves the global-settings draft and persistence, ' + 'room-only suppression before area work, immediate stale-room-tip cleanup, preserved room ' + 'highlight/device tooltip and default-on restoration (#426)', }, { symbols: [ 'applyDevicePlacement', 'devicePlacement', 'sameDevicePlacement', 'DevicePlacement', 'DevicePositionState', ], smokes: ['smoke_device_position_history.mjs'], because: 'the #74 production-bundle scenario drives real Device editor pointer events and ' + 'observes transaction boundaries, point-wise update/delete, toolbar history, native field ' + 'shortcuts, cancellation and revision invalidation; none of those browser effects can be ' + 'proved by the pure placement helpers alone', }, { symbols: [ 'resolveValueSource', 'valueSourceWriteFields', 'valueBadgeCandidates', 'valueBadgeSourceKey', 'resolveDevicePresentation', ], smokes: ['smoke_value_face_source.mjs'], because: 'the #378 production-bundle scenario observes the shared source resolver through ' + 'the real Device dialog, draft preview, persistence, both plan renderers, unavailable ' + 'recovery, action dispatch and binding reset; those DOM effects do not call the pure ' + 'source helpers by name', }, { symbols: ['roomClimateKey', 'roomClimateMap'], smokes: ['smoke_room_climate_placement.mjs'], because: 'the #317 production-bundle scenario proves one manually placed sensor through ' + 'room label, tooltip, temperature fill and hosted Static; those DOM surfaces cannot call ' + 'the pure room-target helpers directly', }, { symbols: [ 'LanguageRuntime', 'LanguageRuntimeContract', 'languageRenderGate', 'dictionaryFor', 'ensureLanguage', ], smokes: ['smoke_german_locale.mjs', 'smoke_french_locale.mjs'], because: 'the production-bundle scenario observes the lazy German chunk through network ' + 'requests and four root render gates: neutral cold frame, atomic German commit, page-cache ' + 'reuse and the bounded retry-to-English path cannot be proven from pure registry tests (#348)', }, { symbols: [ 'namespaceLanguageRuntime', 'composeLanguageRuntimes', 'surfaceLanguageRuntime', 'SETTINGS_LANGUAGE_RUNTIME', 'SUPPORT_LANGUAGE_RUNTIME', 'TOPOLOGY_LANGUAGE_RUNTIME', 'EDITOR_LANGUAGE_RUNTIME', 'ONBOARDING_LANGUAGE_RUNTIME', 'settingsT', 'supportT', 'topologyT', ], smokes: ['smoke_lazy_admin_locale.mjs', 'smoke_dialog_segments_i18n.mjs'], because: 'the ru/de/fr namespace dictionaries are separate lazy chunks: only the production ' + 'bundle shows the per-frame dialog text on a cold open, the held frame on a live switch, ' + 'the bounded retry-to-English path and the exact chunk requests of each surface (#627)', }, { symbols: [ 'probeBackdrop', 'classifyPlanFile', 'downscaleBackdrop', 'renderBackdropGuard', 'WARN_DECODED_BYTES', 'HARD_DIMENSION', 'DOWNSCALE_TARGET_PX', ], smokes: ['smoke_backdrop_guard.mjs'], because: 'the production-bundle scenario proves the guard end to end: header-only warning ' + 'with zero decode calls, byte-identical keep-original path, a real 6200px reduce to 4096 ' + 'with alpha preserved, the cancel-only hard dialog, the honest phase-2 failure (toast, ' + 'clean staging, no silent original upload) and the SVG bypass (#39)', }, { symbols: [ 'stagePlanFile', 'uploadPlanFile', 'renderPlanBackdropGuard', 'MAX_PLAN_BYTES', 'MAX_PLAN_MB', 'PLAN_UPLOAD_PATH', ], smokes: [ 'smoke_plan_upload_limit.mjs', 'smoke_plan_upload_race.mjs', 'smoke_plan_upload_reject.mjs', 'smoke_backdrop_guard.mjs', ], because: '#617: the smokes drive _pickPlanFile/_saveSpaceDialog of both runtimes on the ' + 'production bundle and observe the transport (one multipart POST, zero plan/set), the ' + 'pre-send limit toast, the reduced-copy-only guard and the 413 text; none of them names ' + 'the shared helpers, so a name search cannot find them', }, { symbols: ['vacMapIdWithFallback', 'vacMapIdFromAttrs', 'readVacTelemetry'], smokes: ['smoke_cold_view_vacuum.mjs'], because: 'the telemetry-bearing cold scenario proves the vacuum render path (willUpdate ' + 'snapshot, map-id fallback with selected_map: 0, live position frames) survives without ' + 'the lazy editor runtime — the demo mower has no position attributes, so every other ' + 'smoke skips this branch (#358)', }, { symbols: ['TAP_ACTIONS', 'DeviceTapAction'], smokes: ['smoke_tap_ctx.mjs'], because: 'the Device editor exposes the canonical ordered action list and the same scenario ' + 'proves explicit none consumes pointer and keyboard activation without UI, feedback or ' + 'HA side effects (#381)', }, { symbols: [ 'resolveToggleIntent', 'toggleOperation', 'projectedTapAction', 'toggleOriginOf', 'resolvedLightSources', ], smokes: ['smoke_cold_view_toggle.mjs'], because: 'the cold-view scenario proves a plain tap resolves and executes WITHOUT the lazy ' + 'editor runtime: a wall switch drives three passive virtual lamps, a controlled lamp ' + 'drives its switch, tap_confirm renders and confirms, and no editor chunk is requested ' + '(#357 — every warm smoke preloads the runtime and cannot see this class)', }, { symbols: ['quantizeKeyCoord', 'INCIDENT_EPS', 'KEY_FACTOR'], smokes: ['smoke_junction_limits.mjs'], because: 'the smoke proves the write barrier verdicts (refusal toasts, resize stop, ' + 'fail-closed candidate) through the rendered card, never naming the key-quantisation ' + 'internals that decide which nodes are one node (#331 §2.1)', }, { symbols: [ 'EditorRuntimeLoader', 'EditorRuntimeLoaderState', 'EditorRuntimeModule', 'EditorRuntimeLoaderFailure', 'lazyLoadFailureMessage', 'EDITOR_RUNTIME_FINGERPRINT', 'createHouseplanEditorRuntime', ], smokes: ['smoke_lazy_editor_chunk.mjs', 'smoke_entry_stale.mjs'], because: 'the production-bundle scenarios prove that the content-hashed editor chunk stays ' + 'off the View network path, is shared by all three editors, retries once per cycle, ' + 're-arms after a network failure so the next press heals (#353), refuses an incompatible ' + 'build before editor state is installed (#337), and that a stale cached entry shows a ' + 'localized reload panel instead of dying silently (#353)', }, { symbols: [ 'ROOM_GEAR_DRAG_THRESHOLD_PX', 'roomGearPointAllowed', 'roomGearAutoCenter', 'resolveRoomGearCenter', 'roomGearDragMoved', 'clampRoomGearPointAlongPath', 'RoomGearDragController', '_cancelRoomGearForMultitouch', ], smokes: ['smoke_room_gear_drag.mjs', 'smoke_resize_labels.mjs'], because: 'the #645 production gestures prove temporary room-button movement, click/drag ' + 'arbitration, room-boundary clamping, cancel/multitouch rollback and session reset; the ' + 'Resize smoke proves its live area labels avoid the actually rendered room button', }, { symbols: [ 'placeResizeAreaLabel', 'resizeInwardNormal', 'resizeMeasuredEdges', 'ResizeAreaPlacement', 'ResizeAreaPlacementInput', 'ResizeLabelView', ], smokes: ['smoke_resize_labels.mjs'], because: 'the #300 production gesture observes the pure projection only through the ' + 'rendered side-wall highlights, area/leader DOM and actual gear rectangles at current zoom', }, { symbols: [ 'classifyNearAxisSegment', 'snapNearAxisEndpoint', 'repairNearAxisRoomWalls', 'NEAR_AXIS_MAX_DEGREES', 'NEAR_AXIS_MAX_SLOPE', ], smokes: ['smoke_plan_drawing_repairs.mjs', 'smoke_near_axis_optimize.mjs'], because: 'the production bundle must make the Walls hover/click candidate exact-axis, then ' + 'preview one deduplicated real shared wall, pass production geometry preflight, apply one ' + 'atomic Optimize write, reload idempotently and restore the original through one Undo (#290)', }, { // #297: обход не проверяет снимок — он расшатывает план продуктовыми жестами // и спрашивает инварианты после каждого. Поиском по тексту связь между этим // смоком и кодом ресайза, удаления комнаты и «Оптимизировать» не находится. symbols: [ 'resolveSafeResize', 'applySafeResize', 'clampSafeResize', 'validateSafeResize', 'planRoomDeletion', 'materializeWallIntervals', 'optimizePlans', 'reconcileCoincidentPartitions', ], smokes: ['smoke_edit_walk.mjs'], because: 'дефекты геометрии рождаются в редактировании, а не в хранении: #289, #296 и ' + '#298 прошли все снимковые гейты — обход находит их на первом-втором жесте', }, { // #285: смок не произносит ни одного имени продуктового кода — он подставляет // геометрию реального этажа и спрашивает сам продукт через isPointInFill, // есть ли кладка там, где модель её обещает. Поиском по тексту такая связь // не находится никогда. symbols: [ 'wallBodiesGeometry', 'wallEdgeBodies', 'buildMultiWallNodeMap', 'multiWallBevelTriangles', 'virtualJunctionPatches', 'linearWallJoinPatches', 'MITRE_LIMIT', 'MULTI_WALL_JOIN_LIMIT', 'insetContour', 'atomicPolyForRoom', ], smokes: ['smoke_real_plan_masonry.mjs'], because: 'на реальном плане кладка рвётся там, где синтетические фикстуры целы: ' + 'восемь закрытых задач по стыкам вошли в beta.9, а разрыв в 45 шагов остался — ' + 'пиксельные пороги golden такую долю кадра не видят', }, { symbols: [ 'WallGeometryStatus', 'WallGeometryComponent', 'WallBodiesGeometryResult', 'checkSpacePhysicalGeometry', 'spacePhysicalGeometryFingerprint', ], smokes: ['smoke_wall_union_isolation.mjs'], because: 'the #278 production bundle must paint isolated canonical components in Plan, ' + 'View, Static, hidden Iso and light barriers, reject a physical write with zero WS/Undo, ' + 'and still allow a non-geometry edit on the same degraded legacy space', }, { symbols: [ 'resolveSafeResize', 'applySafeResize', 'validateSafeResize', 'clampSafeResize', 'safeResizePointerDisplacement', 'SafeResizePlan', 'SafeResizeResolution', 'SafeResizeReason', 'SafeResizeObstacle', ], smokes: ['smoke_room_resize.mjs', 'smoke_resize_pointer_real_plan.mjs'], because: 'the #277 synthetic scenario covers fixed topology, first-corner clamp and disabled ' + 'accessibility, while #293 recreates the card from config/get and drives real browser mouse ' + 'events on the tracked second-floor plan; pure helpers are hidden behind both controllers', }, { symbols: ['reconcileCoincidentPartitions', 'CoincidentPartitionResult'], smokes: ['smoke_optimize_coincident_partition.mjs'], because: 'the production-bundle scenario observes the Optimize report, atomic Apply, reload, ' + 'one-shot Undo and the resulting Boundary/Thickness targets, while the pure reconciliation ' + 'helper is not exposed on the card instance (#276)', }, { // #263: пикер называет себя (`_bindingCandidates`) и потому находится // прямым совпадением. Регистрируются только чистые помощники надгробий: // смок не произносит ни одного их имени — он видит лишь список, который // они отфильтровали. Проверено зондом: правка одного `src/devices.ts` без // этой записи не выбирает ни одного смока. symbols: ['removedPlanBindings', 'isRemovedPlanEntity', 'deletePlanMarkerRecords'], smokes: ['smoke_binding_picker.mjs'], because: 'смок проверяет, что удалённая привязка снова предлагается в списке «Добавить», ' + 'что отдельные сущности устройств живут за галкой и что надгробие устройства ' + 'по-прежнему прячет его дочерние сущности (#262) — всё это следствия фильтров ' + 'надгробий, чьи имена в браузерный сценарий не попадают', }, { // #261: the browser observes the canonical wall/paper paths and downstream // geometries, while the pure bevel helpers remain internal to the bundle. symbols: [ 'multiWallBevelTriangles', 'multiWallBevelTrianglesAt', 'buildMultiWallNodeMap', 'MultiWallNodeRay', 'MultiWallNodeRaySupport', 'multiWallProtectedRayIndexes', 'multiWallProtectedStripGeometry', 'multiWallProtectedMapGeometry', 'multiWallEffectiveCutGeometry', ], smokes: [ 'smoke_junction_patch_resilience.mjs', 'smoke_multiwall_junction.mjs', 'smoke_multiwall_strip_containment.mjs', ], because: 'the #197 smoke probes the repaired exterior wedge across Plan, View, kiosk, ' + 'Static, hidden Iso and light barriers, while the #249 smoke proves the old excessive ' + 'wedge stays empty, #271 also proves finite ray endpoints, and #275 densely samples every ' + 'protected orthogonal strip across Plan, Static, hidden Iso and light barriers; the browser ' + 'scenarios call none of the pure helpers by name', }, { // #258: the browser sees only the resulting path/caches; it cannot call // the pure identity helpers by name through the production bundle. symbols: ['wallKey', 'lookupWall', 'canonicalKeyCoordinate', 'keyEpsilon'], smokes: ['smoke_wall_key_roundtrip.mjs'], because: 'the affected and canonical midpoint keys must produce the same T-junction in ' + 'Plan, View, kiosk, Static, hidden Iso, clean-floor and light-barrier consumers', }, { // #253: the pure interval transformer is invoked inside the resize preview; // the smoke can observe only the resulting wall records and rendered body. symbols: [ 'rekeyWallsAfterMove', 'WallEntry', 'angleClose', 'clampWallCm', 'closePoint', 'distToSeg', 'keyOf', 'pointAt', 'segAngle', 'wallDir', 'wallEntry', ], smokes: ['smoke_resize_wall_thickness.mjs', 'smoke_resize_pointer_real_plan.mjs'], because: 'the real pointer-handler smoke proves that a partial overlap is split losslessly ' + 'through live preview, commit, opening movement and Undo, but the browser bundle does ' + 'not expose the pure transformer name to the scenario', }, { // #244: pure repair/validation helpers are bundled behind editor actions; // the smoke observes their persisted candidate and rendered explanations. symbols: [ 'repairSpaceReferences', 'collectSpaceMarkerDependencies', 'createSpaceDeletionCandidate', 'invalidDefaultFloor', 'ImportLineageRoot', 'canonicalImportRoot', 'reversibleStem', 'addCandidate', ], smokes: ['smoke_orphan_space_references.mjs'], because: 'the production-bundle smoke proves the Optimize candidate, live marker rebuild, ' + 'nested import-lineage collapse, delete blocker and card-editor warning, while those UI ' + 'surfaces do not expose the pure helper names at runtime', }, { // #242: pure placement is called inside shared SVG/Iso renderers, while // browser smokes can only inspect the resulting transforms and bases. symbols: ['openingSymbolOffset'], smokes: ['smoke_isometric_contract.mjs', 'smoke_wall_thickness.mjs'], because: 'wall-thickness smoke proves the default and saved-flip SVG translations; ' + 'isometric smoke inspects door/window/gate bases from the same helper, but neither ' + 'browser bundle exposes the helper name in its test steps', }, { // #234: единый резолвер толщины отрезка цепочки. Смок перехода между // толщинами не называет ни `chainSegmentCms`, ни `_wallChainSegmentCms` — он // рисует стены инструментом и проверяет, что кладка на стыке толщин // остаётся связной. Общих с диффом символов у него ровно два, `_cellCm` и // `_gridPitch`, и оба слишком широкие, чтобы что-то доказывать. symbols: ['chainSegmentCms', 'wallChainSegments', '_wallChainSegmentCms', '_closingWallCm'], smokes: ['smoke_wall_thickness_transition.mjs'], because: 'переход между двумя толщинами в одной цепочке: резолвер толщины ' + 'отрезка решает, где кладка меняет глубину, а смок проверяет кладку, ' + 'а не толщину, и ни одного имени из резолвера не называет', }, ]; /** Смоки, связанные с изменёнными символами через реестр. */ export function registeredSmokes(changedSymbols) { const changed = new Set(changedSymbols); const out = new Map(); for (const link of SMOKE_LINKS) { const hit = link.symbols.filter((symbol) => changed.has(symbol)).sort(); if (!hit.length) continue; for (const smoke of link.smokes) { const entry = out.get(smoke) || { smoke, symbols: [], because: [] }; entry.symbols = [...new Set([...entry.symbols, ...hit])].sort(); if (!entry.because.includes(link.because)) entry.because.push(link.because); out.set(smoke, entry); } } return [...out.values()].sort((a, b) => a.smoke.localeCompare(b.smoke)); }