Skip to content

feat: add HVAC specialty-tank components (heat pump, hydraulic separator, heat exchanger) - #1054

Open
ludvigovrevik wants to merge 6 commits into
developfrom
feat/specialty-tank-components
Open

feat: add HVAC specialty-tank components (heat pump, hydraulic separator, heat exchanger)#1054
ludvigovrevik wants to merge 6 commits into
developfrom
feat/specialty-tank-components

Conversation

@ludvigovrevik

@ludvigovrevik ludvigovrevik commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds three new automation instrument tiles that share one abstract base class (the pump/fan/motor pattern):

Component Tag Equipment icon Fill split Divider
Heat pump obc-heat-pump obi-heatpump vertical (L hot / R cold) vertical bar
Hydraulic separator obc-hydraulic-separator obi-hydraulic-separator horizontal (top hot / bottom cold) horizontal bar
Heat exchanger obc-heat-exchanger obi-heatexhanger diagonal triangles diagonal bar (SVG, corner-to-corner)

ObcAbstractSpecialtyTank (src/automation/specialty-tank/, not a custom element) owns the frame, badge row, hot/cold medium fill, corner glyphs, icon frame, tag readout, and alert-frame overlay. Each subclass overrides two getters: equipmentIcon and splitMode.

Figma (OpenBridge 6.1)

Implemented 1:1 against the design nodes, both hasMedium states, via the Figma MCP:

Details taken from Figma: 4px state-container padding + 2px value-padding gap, 6px halo radius, always-visible 4px divider with 1px --instrument-frame-tertiary-color edges, 4px-inset content area with 4px radius, glyphs --element-inactive-color when grey / --base-red-500 + --base-blue-500 when medium, equipment icon --element-active-color. First consumer of the --automation-components-tanks-specialty-tanks-* tokens.

API

  • medium (false), showMediumIcons (true), showTag (true) + tag — mirroring the Figma props hasMedium / hasIcons / hasIdTag
  • Badges reuse the ObcAbstractAutomationButton enums (badgeControl, badgeInterlock = Figma Duty, badgeCommandLocked, badgeAlert) with a badges slot override; the row auto-hides when empty (Figma hasBadges=false default)
  • Alert-frame mirror of obc-automation-tank: alert, alertFrameType/Thickness/Status, showAlertCategoryIcon, showAlertIcon + alert-icon/alert-label/alert-timer slots

Testing

  • typecheck, eslint, lit-analyzer, lint:variables all green
  • 4 stories per component (Default, WithMedium, WithBadges, WithAlert) under Automation/Tanks, 12 visual tests passing locally; renders compared frame-by-frame against the Figma screenshots
  • Linux visual baselines are not included — they should be taken from this PR's first Playwright Tests run (win32 renders differ from CI)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added new automation specialty-tank web components: heat exchanger, heat pump, and hydraulic separator.
    • Introduced shared specialty-tank UI capabilities, including hot/cold medium rendering, vertical/horizontal/diagonal split modes, tags, badge states, corner indicators, and optional alert-frame overlays.
    • Added Storybook coverage for each component (Default, Medium-enabled, Badges, and Alert variants).
  • Chores
    • Updated ignore rules to exclude a Windows-specific visualization output directory.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces a reusable specialty-tank Lit base with medium fills, badges, tags, alert frames, and split geometries. Adds heat-exchanger, heat-pump, and hydraulic-separator elements with custom icons and Storybook stories for default, medium, badge, and alert states.

Changes

Specialty tank automation tiles

Layer / File(s) Summary
Specialty tank base behavior
packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts
Defines reactive properties, badge and slot handling, medium-fill rendering, tags, alert overlays, and split modes.
Specialty tank visual layout
packages/openbridge-webcomponents/src/automation/specialty-tank/specialty-tank.css
Styles tile sizing, fills, dividers, badges, tags, icons, and alert framing.
Concrete specialty tile elements
packages/openbridge-webcomponents/src/automation/{heat-exchanger,heat-pump,hydraulic-separator}/*.ts
Registers three custom elements with distinct icons, split modes, names, and TypeScript tag mappings.
Storybook controls and variants
packages/openbridge-webcomponents/src/automation/{heat-exchanger,heat-pump,hydraulic-separator}/*.stories.ts
Adds typed controls and four stories for each component.
Visualization output exclusion
.gitignore
Ignores Windows visualization output.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Storybook
  participant SpecialtyTile
  participant ObcAbstractSpecialtyTank
  participant ObcAlertFrame
  Storybook->>SpecialtyTile: Bind story args
  SpecialtyTile->>ObcAbstractSpecialtyTank: Render icon and split mode
  ObcAbstractSpecialtyTank->>ObcAlertFrame: Render overlay when alert is enabled
  ObcAbstractSpecialtyTank-->>Storybook: Return composed tile
Loading

Suggested labels: enhancement, Instrument Component

Suggested reviewers: tibnor, jon-daeh, ulrik-jo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change by naming the new HVAC specialty-tank components added in this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/specialty-tank-components

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts (4)

158-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate slot-change detection logic.

_onBadgesSlotChange and _onTagSlotChange implement identical "has meaningful assigned content" logic, differing only in which @state field they write. Extract a shared helper.

♻️ Proposed refactor
-  private _onBadgesSlotChange(e: Event): void {
-    const slot = e.target as HTMLSlotElement;
-    this._hasBadges = slot
-      .assignedNodes({flatten: true})
-      .some(
-        (n) =>
-          n.nodeType === Node.ELEMENT_NODE ||
-          (n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim())
-      );
-  }
-
-  private _onTagSlotChange(e: Event): void {
-    const slot = e.target as HTMLSlotElement;
-    this._hasTagSlot = slot
-      .assignedNodes({flatten: true})
-      .some(
-        (n) =>
-          n.nodeType === Node.ELEMENT_NODE ||
-          (n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim())
-      );
-  }
+  private _slotHasContent(e: Event): boolean {
+    const slot = e.target as HTMLSlotElement;
+    return slot
+      .assignedNodes({flatten: true})
+      .some(
+        (n) =>
+          n.nodeType === Node.ELEMENT_NODE ||
+          (n.nodeType === Node.TEXT_NODE && !!n.textContent?.trim())
+      );
+  }
+
+  private _onBadgesSlotChange(e: Event): void {
+    this._hasBadges = this._slotHasContent(e);
+  }
+
+  private _onTagSlotChange(e: Event): void {
+    this._hasTagSlot = this._slotHasContent(e);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts`
around lines 158 - 178, Extract the duplicated assigned-content check from
_onBadgesSlotChange and _onTagSlotChange into a shared helper that accepts an
HTMLSlotElement and returns whether it has meaningful assigned nodes. Update
both handlers to use the helper while preserving their respective _hasBadges and
_hasTagSlot assignments.

297-311: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Generic 'Tank' aria-label fallback doesn't distinguish equipment type.

When tag is unset, all three subclasses (heat pump, hydraulic separator, heat exchanger) fall back to the same generic aria-label="Tank", giving screen-reader users no indication of which equipment tile they're interacting with.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts`
around lines 297 - 311, Update the aria-label in the render template of the
abstract specialty tank component to use an equipment-specific fallback when tag
is unset, so heat pump, hydraulic separator, and heat exchanger subclasses
expose distinct accessible names while preserving the configured tag when
present.

87-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prefer TypeScript abstract over runtime throw for unimplemented getters.

Since this class isn't a @customElement (never directly instantiated), it can be declared abstract class ObcAbstractSpecialtyTank extends LitElement with abstract get equipmentIcon(): TemplateResult; and abstract get splitMode(): SpecialtyTankSplitMode;. This catches missing overrides at compile time instead of only at render-time.

♻️ Proposed refactor
-export class ObcAbstractSpecialtyTank extends LitElement {
+export abstract class ObcAbstractSpecialtyTank extends LitElement {
   ...
-  get equipmentIcon(): TemplateResult {
-    throw new Error('Method "equipmentIcon" must be implemented in subclass');
-  }
-
-  get splitMode(): SpecialtyTankSplitMode {
-    throw new Error('Method "splitMode" must be implemented in subclass');
-  }
+  abstract get equipmentIcon(): TemplateResult;
+
+  abstract get splitMode(): SpecialtyTankSplitMode;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts`
around lines 87 - 93, Declare ObcAbstractSpecialtyTank as an abstract class and
mark its equipmentIcon and splitMode getters abstract, removing their runtime
error-throwing implementations so subclasses must provide both members at
compile time.

36-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Base-class JSDoc lacks the mandated structure.

The class doc is a short overview but omits the required Slots section (the class renders badges, tag, alert-icon, alert-label, alert-timer slots), usage guidance, and features/variants. Since this is an abstract base whose description is meant to be reused/overridden in the concrete subclasses' Storybook docs, it should carry the full structured documentation.

As per coding guidelines: "For Web Components, document all content slots in a Slots section, including each slot's name, when it renders, and its purpose; omit the section only if the component does not use slots" and "place a JSDoc block on the class or module and structure it with an overview, features/variants, usage guidance, slots/content, events, best practices."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts`
around lines 36 - 48, Expand the class-level JSDoc for the abstract
specialty-tank base with the required structured sections: overview,
features/variants, usage guidance, and slots/content. Document the badges, tag,
alert-icon, alert-label, and alert-timer slots, including when each renders and
its purpose; include events and best practices when applicable, and ensure the
documentation is suitable for reuse by concrete subclasses.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts`:
- Around line 36-45: Reorder the JSDoc tag blocks so the Ignition tags and `@beta`
appear before the final slot/event-only block in heat-exchanger.ts (lines
36-45), heat-pump.ts (lines 36-45), and hydraulic-separator.ts (lines 36-45).
Preserve all existing tags and ensure each component’s JSDoc ends with only its
`@slot` and any `@fires/`@event tags.

---

Nitpick comments:
In
`@packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts`:
- Around line 158-178: Extract the duplicated assigned-content check from
_onBadgesSlotChange and _onTagSlotChange into a shared helper that accepts an
HTMLSlotElement and returns whether it has meaningful assigned nodes. Update
both handlers to use the helper while preserving their respective _hasBadges and
_hasTagSlot assignments.
- Around line 297-311: Update the aria-label in the render template of the
abstract specialty tank component to use an equipment-specific fallback when tag
is unset, so heat pump, hydraulic separator, and heat exchanger subclasses
expose distinct accessible names while preserving the configured tag when
present.
- Around line 87-93: Declare ObcAbstractSpecialtyTank as an abstract class and
mark its equipmentIcon and splitMode getters abstract, removing their runtime
error-throwing implementations so subclasses must provide both members at
compile time.
- Around line 36-48: Expand the class-level JSDoc for the abstract
specialty-tank base with the required structured sections: overview,
features/variants, usage guidance, and slots/content. Document the badges, tag,
alert-icon, alert-label, and alert-timer slots, including when each renders and
its purpose; include events and best practices when applicable, and ensure the
documentation is suitable for reuse by concrete subclasses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7965d583-922b-49a2-9b6c-0e60f483d362

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3a532 and 5e417e6.

📒 Files selected for processing (8)
  • packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.stories.ts
  • packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts
  • packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.stories.ts
  • packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts
  • packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.stories.ts
  • packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts
  • packages/openbridge-webcomponents/src/automation/specialty-tank/abstract-specialty-tank.ts
  • packages/openbridge-webcomponents/src/automation/specialty-tank/specialty-tank.css

Comment on lines +36 to +45
* @slot badges - Custom badges, overriding the enum-driven defaults.
* @slot tag - Text or element replacing the `tag` property readout.
* @slot alert-icon - Custom icon for the alert frame.
* @slot alert-label - Label for the alert frame.
* @slot alert-timer - Timer for the alert frame.
*
* @ignition-base-width: 90px
* @ignition-base-height: 163px
* @ignition-center-horizontal
* @beta

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

End each component JSDoc with the slot/event block. The @ignition-* and @beta tags currently follow @slot tags, so the required final tag block is not slot/event-only.

  • packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts#L36-L45: move the Ignition and beta tags before a final @slot block.
  • packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts#L36-L45: move the Ignition and beta tags before a final @slot block.
  • packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts#L36-L45: move the Ignition and beta tags before a final @slot block.

As per coding guidelines, “append a final tag block containing only @slot and @fires/@event tags.”

📍 Affects 3 files
  • packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts#L36-L45 (this comment)
  • packages/openbridge-webcomponents/src/automation/heat-pump/heat-pump.ts#L36-L45
  • packages/openbridge-webcomponents/src/automation/hydraulic-separator/hydraulic-separator.ts#L36-L45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/openbridge-webcomponents/src/automation/heat-exchanger/heat-exchanger.ts`
around lines 36 - 45, Reorder the JSDoc tag blocks so the Ignition tags and
`@beta` appear before the final slot/event-only block in heat-exchanger.ts (lines
36-45), heat-pump.ts (lines 36-45), and hydraulic-separator.ts (lines 36-45).
Preserve all existing tags and ensure each component’s JSDoc ends with only its
`@slot` and any `@fires/`@event tags.

Source: Coding guidelines

ludvigovrevik and others added 6 commits July 20, 2026 21:18
Heat pump, hydraulic separator, and heat exchanger share one abstract
base (ObcAbstractSpecialtyTank); subclasses override only the center
icon and the hot/cold split geometry. Layout, tokens, divider, and
glyph colors verified 1:1 against Figma nodes 18907-47623 / 18907-47658
/ 18907-47693 in both hasMedium states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Baselines generated through the CI-matching Docker image and verified
stable with a second run. WithBadges stories switch the alert badge to
alert-silenced, matching the default badge set on the Figma tiles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows-generated __vis__/win32 snapshots are local-only (CI compares
the linux baselines), mirroring the existing darwin ignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- declare the base abstract with abstract getters (compile-time check)
- equipment-specific aria-label fallback via abstract equipmentName
- deduplicate slot-content detection into a shared helper
- full structured JSDoc on the base; end subclass JSDoc with slot tags

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generated React wrapper passes every manifest class to
createComponent as Constructor<T>; an abstract constructor is not
assignable and broke the wrapper build. Revert to the runtime-throw
override points used by ObcAbstractAutomationButton and document why.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documents the Figma role mapping (Mode / Command / Alert badge order),
the tag readout, and the alert overlay inline so the manifest and
Storybook autodocs carry per-property descriptions, per the inline
property-doc policy from #1046.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ludvigovrevik
ludvigovrevik force-pushed the feat/specialty-tank-components branch from a977093 to f043274 Compare July 20, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant