Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
77f1381
docs: add compass/heading center readouts & arrow styles design spec
talpitoo Jul 23, 2026
476b28f
docs: extend spec scope — migrate pitch-roll/compass-sector onto shar…
talpitoo Jul 23, 2026
05f99f2
docs: add implementation plan for compass/heading center content
talpitoo Jul 23, 2026
5086f6e
refactor(instruments): extract shared course-arrows module behind leg…
talpitoo Jul 23, 2026
8c32578
feat(instruments): add vector, beam-line and velocity-vector arrow st…
talpitoo Jul 23, 2026
fb92e32
feat(instruments): add shared center-readout cluster helper
talpitoo Jul 23, 2026
d84c30c
refactor(pitch-roll): render center readouts via shared cluster helper
talpitoo Jul 23, 2026
4578458
refactor(compass-sector): render readout via shared cluster helper
talpitoo Jul 23, 2026
b1f48bd
feat(watch): add CCRP vessel image (psv-top-ccrp)
talpitoo Jul 23, 2026
7d1db20
feat(heading): vessel, center readouts and arrow styles; document com…
talpitoo Jul 23, 2026
734e095
feat(compass): center readouts, arrow styles and map-graphics recipe
talpitoo Jul 23, 2026
88103b3
docs: remove implementation plan from branch
talpitoo Jul 23, 2026
e67968a
docs: remove design spec from branch
talpitoo Jul 23, 2026
833e6d8
refactor(instruments): use existing psv-top vessel in stories, drop C…
talpitoo Jul 23, 2026
b57eace
docs(stories): tag the new compass/heading stories as 6.1
talpitoo Jul 23, 2026
79c834f
feat(compass-flat): bottom heading readout (hasReadout)
talpitoo Jul 23, 2026
6528aa0
feat(rate-of-turn): center readout and track bar with end needle
talpitoo Jul 23, 2026
b8f9456
feat(rot-linear): new linear rate-of-turn strip component
talpitoo Jul 23, 2026
9ba847c
docs(agents): availableWhen empty-array grammar, --update order fix, …
talpitoo Jul 23, 2026
77c885f
fix(rot-linear): guard rotMaxValue against non-finite values; widen i…
talpitoo Jul 23, 2026
b5ca864
feat(watch): crosshair center cutout under center readouts
talpitoo Jul 27, 2026
3aa1eff
refactor(speed-arrows): extract chevron art into shared module
talpitoo Jul 27, 2026
af5a19e
feat(speed-directions): geometry table and speed mapping helpers
talpitoo Jul 27, 2026
f96f41c
fix(speed-directions): avoid negative zero in athwartMid cy calculation
talpitoo Jul 27, 2026
d11cc23
feat(speed-directions): new instrument with standalone arrow types
talpitoo Jul 27, 2026
1323e15
fix(speed-directions): svg fill=none, vessel stroke counter-scaling, …
talpitoo Jul 27, 2026
127aa46
feat(speed-directions): along athwart bars type; top-anchor bar tip c…
talpitoo Jul 27, 2026
ac8c9e9
feat(speed-directions): framed style with rounded-square track and ax…
talpitoo Jul 27, 2026
fa7fde6
feat(speed-directions): compass style on obc-watch face
talpitoo Jul 27, 2026
7353afe
fix(speed-directions): keep class JSDoc attached to the component class
talpitoo Jul 27, 2026
c06bc63
feat(speed-directions): cap compass axis lines at ring, sizing storie…
talpitoo Jul 27, 2026
b1a4bba
fix(speed-directions): compass athwart axis lines are chords of the disc
talpitoo Jul 27, 2026
1776226
fix(watch): generic-top vessel outline as non-scaling stroke
talpitoo Jul 27, 2026
52e1d6c
docs(watch): TODO pointing generic-top at its Figma source and converter
talpitoo Jul 27, 2026
7827a65
feat(watch): propeller image assets and registry
talpitoo Jul 27, 2026
11d2cc3
feat(top-view-propulsion): new WIP instrument, power type
talpitoo Jul 27, 2026
81100e4
feat(top-view-propulsion): pitch-rpm type with secondary pitch arc
talpitoo Jul 27, 2026
bc3759e
fix(top-view-propulsion): pitch arc with flat start and rounded end o…
talpitoo Jul 27, 2026
4e7f391
fix(top-view-propulsion): subdivide the band into primary, divider an…
talpitoo Jul 27, 2026
0e6cf97
docs: address coderabbit review on shared art, geometry table and ava…
talpitoo Jul 27, 2026
a726426
feat(watch): arc tick ladder on the inclinometer arcs
talpitoo Jul 28, 2026
e43440e
feat(readout): stacked center-readout arrangement
talpitoo Jul 28, 2026
bfd3b3c
feat(heave): bar type rendering the scale column only
talpitoo Jul 28, 2026
37c3ab1
feat(pitch-roll-heave): new instrument with a derived heave column
talpitoo Jul 28, 2026
8c6da40
docs(instructions): pitch-roll-heave geometry contract and shared mod…
talpitoo Jul 28, 2026
52adad5
refactor(instrument-linear): share advice, tickmark and mask-id helpers
talpitoo Jul 28, 2026
0079a72
feat(draft-trim): new fore and aft draught instrument
talpitoo Jul 28, 2026
6f44b30
docs(draft-trim): address review on tick contract, story name and JSDoc
talpitoo Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/instructions/building-blocks.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ When working with building block components in this directory:
- Height defines the visual size and coordinate system scale
- Padding (top/bottom or left/right) is typically added outside the main drawing area

12. **`instrument-linear` shared helpers** (consumed by `obc-heave`, `obc-draft-trim`):
- `resolveLinearAdvice(advice, trendMin, trendMax)` resolves each advice zone's
hinted/regular/triggered state against a trend band.
- `verticalScaleTickmarks(range)` returns the tick configuration for a scale
spanning `±range` around a zero reference: a full-width line at zero, plus a
primary and a secondary ladder whose intervals step with the range — `1`/`0.5`
for a range of 5 or less, `5`/`1` above that.
- `linearTickInterval(height, range, minSpacing)` picks a 1-2-5 tick step by
on-screen spacing, for scales whose height is dictated by a surrounding layout.
- `watchfaceLinear`'s `options.maskId` overrides the container clip-mask id.
**Ids are document-scoped**, so any component rendering more than one gauge in
a single shadow root MUST give each a distinct id — otherwise every gauge
resolves to the first one's mask.
- **Scale space is positive-up.** A quantity measured downward (a draught) has to
be negated, and so do its advice bounds — which additionally swap, since
negating an interval reverses it: `{min, max}` → `{min: -max, max: -min}`.

### `packages/openbridge-webcomponents/src/svghelpers/**`

When working with SVG helper utilities:
Expand Down
125 changes: 109 additions & 16 deletions .github/instructions/watch-radial-instruments.instructions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
applyTo: "packages/openbridge-webcomponents/src/navigation-instruments/watch/**,packages/openbridge-webcomponents/src/navigation-instruments/compass/**,packages/openbridge-webcomponents/src/navigation-instruments/heading/**,packages/openbridge-webcomponents/src/navigation-instruments/rudder/**,packages/openbridge-webcomponents/src/navigation-instruments/wind/**,packages/openbridge-webcomponents/src/navigation-instruments/roll/**,packages/openbridge-webcomponents/src/navigation-instruments/speed-gauge/**,packages/openbridge-webcomponents/src/navigation-instruments/gauge-radial/**,packages/openbridge-webcomponents/src/navigation-instruments/rot-sector/**,packages/openbridge-webcomponents/src/navigation-instruments/azimuth-thruster/**,packages/openbridge-webcomponents/src/building-blocks/instrument-radial/**"
applyTo: "packages/openbridge-webcomponents/src/navigation-instruments/watch/**,packages/openbridge-webcomponents/src/navigation-instruments/compass/**,packages/openbridge-webcomponents/src/navigation-instruments/compass-sector/**,packages/openbridge-webcomponents/src/navigation-instruments/heading/**,packages/openbridge-webcomponents/src/navigation-instruments/rudder/**,packages/openbridge-webcomponents/src/navigation-instruments/wind/**,packages/openbridge-webcomponents/src/navigation-instruments/pitch/**,packages/openbridge-webcomponents/src/navigation-instruments/roll/**,packages/openbridge-webcomponents/src/navigation-instruments/pitch-roll/**,packages/openbridge-webcomponents/src/navigation-instruments/pitch-roll-heave/**,packages/openbridge-webcomponents/src/building-blocks/single-axis-inclinometer/**,packages/openbridge-webcomponents/src/navigation-instruments/speed-gauge/**,packages/openbridge-webcomponents/src/navigation-instruments/gauge-radial/**,packages/openbridge-webcomponents/src/navigation-instruments/rot-sector/**,packages/openbridge-webcomponents/src/navigation-instruments/rate-of-turn/**,packages/openbridge-webcomponents/src/navigation-instruments/course-arrows/**,packages/openbridge-webcomponents/src/navigation-instruments/readout/**,packages/openbridge-webcomponents/src/navigation-instruments/watch-flat/**,packages/openbridge-webcomponents/src/navigation-instruments/compass-flat/**,packages/openbridge-webcomponents/src/navigation-instruments/rot-linear/**,packages/openbridge-webcomponents/src/navigation-instruments/azimuth-thruster/**,packages/openbridge-webcomponents/src/building-blocks/instrument-radial/**"
Comment thread
talpitoo marked this conversation as resolved.
---

# GitHub Copilot Custom Instructions
Expand Down Expand Up @@ -96,6 +96,46 @@ When adding new features or fixing bugs:
3. Navigation instruments should only handle: property declarations, domain-specific overlays, and value-to-angle mapping
4. Avoid duplicating rendering logic across instruments

### Shared sibling modules

- **`course-arrows/course-arrows.ts`** — HDG/COG arrow art
(`HdgArrowStyle`: arrowHead, needle, vector, beamLine;
`CogArrowStyle`: arrowHead, needle, vector, velocityVector), consumed by
compass, heading and — via the deprecated `compass/arrow.ts` shim, which
also carries the zoom `radiusOffset` — compass-sector. The new-style art is
generated from Figma into `course-arrows-art.ts`; do not hand-edit its path
data.
- **`readout/center-readout.ts`** — the center / below-strip readout cluster
(`renderCenterReadouts()`, `resolveCompassCenterReadouts()`,
`centerReadoutStyles`), consumed by compass, heading, compass-sector,
pitch-roll, pitch-roll-heave, rate-of-turn, compass-flat and rot-linear. It
reuses the existing `obc-readout` API only (closest match; no new typography
knobs). Two arrangements via `CenterReadoutArrangement`:
`primarySecondary` (default — first entry, one divider, then the rest side by
side) and `stacked` (a divider between every entry, used by
pitch-roll-heave's Pitch/Roll/Heave column). **The default must stay
`primarySecondary`** — every other consumer depends on it.
- **Arc tick ladder** — `tickmark.ts`'s `arcTickmarks()` emits the `secondary`
ladder that runs along an arc band (default interval 5°),
skipping the arc centre (which carries its own `main` mark) and the ends
(the rounded end cap already reads as a boundary). Shared by the
inclinometer family: `single-axis-inclinometer` (pitch, roll), `pitch-roll`
(both the full-watch and zoomed paths) and `pitch-roll-heave`. The zoomed
paths must pass the **clamped** half-extent, not the requested one, so ticks
never fall outside the band that is actually drawn.
- **`building-blocks/instrument-linear/instrument-linear.ts`** —
`linearTickInterval(height, range, minSpacing = 16)` picks a 1-2-5 tick step
whose on-screen spacing clears `minSpacing`, so a linear scale whose height
is dictated by a surrounding layout keeps a readable density. It reproduces
the intervals `obc-heave` hard-codes at its natural 336-unit height, which is
what fixes the constant; `obc-heave` itself still uses its literals so its
baselines do not move.
- **Track-bar recipe** — `obc-rate-of-turn`'s `hasTrackBar` composes existing
`obc-watch` inputs only: `barAreas` (band fill) + `needles` (marker at the
bar end) + sector `tickmarks` on the `double` face. `renderBars` applies
its area cut mask only when `areas` exist, so band bars render reliably on
full-circle faces.

---

## Layering & Stacking Pattern
Expand Down Expand Up @@ -418,19 +458,22 @@ Common instrument CSS variables used in `watch.ts` and helpers:

## Component Quick Reference

| Component | Uses | Key Features |
| ------------------- | --------------------- | ------------------------------------------------------- |
| `obc-watch` | Helper modules | Core renderer - ALL circular rendering logic |
| `instrument-radial` | `obc-watch` | Generic building block with configurable `getAngle()` |
| `compass` | `obc-watch` + overlay | Full compass: HDG/COG arrows, ROT, vessel, wind/current |
| `heading` | `obc-watch` + overlay | Simplified compass: HDG/COG arrows only |
| `rudder` | `obc-watch` + overlay | Half-circle: 40% top clipped, needle variant |
| `speed-gauge` | `obc-watch` + overlay | Speed arc: custom angle mapping, full needle |
| `wind` | `obc-watch` + overlay | Wind rose with histogram |
| `roll` | `obc-watch` | Roll indicator with vessel |
| `gauge-radial` | `instrument-radial` | Thin wrapper adding `enhanced` prop |
| `rot-sector` | `instrument-radial` | Rate of turn sector gauge |
| `azimuth-thruster` | `obc-watch` + overlay | Thruster with angle setpoint and thrust bar |
| Component | Uses | Key Features |
| ------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `obc-watch` | Helper modules | Core renderer - ALL circular rendering logic |
| `instrument-radial` | `obc-watch` | Generic building block with configurable `getAngle()` |
| `compass` | `obc-watch` + overlay | Full compass: HDG/COG arrow styles, ROT, vessel, wind/current, center readouts |
| `heading` | `obc-watch` + overlay | Simplified compass: HDG/COG arrow styles, optional vessel, center readouts |
| `rate-of-turn` | `obc-watch` | ROT dots/bar, track bar (barAreas+needles), center readout |
| `rudder` | `obc-watch` + overlay | Half-circle: 40% top clipped, needle variant |
| `speed-gauge` | `obc-watch` + overlay | Speed arc: custom angle mapping, full needle |
| `wind` | `obc-watch` + overlay | Wind rose with histogram |
| `pitch` / `roll` | `single-axis-inclinometer` → `obc-watch` | Single-axis inclinometer: side arc scale, single/dual scale, optional center readout |
| `pitch-roll` | `obc-watch` | Pitch + roll on one face; 4 arcs, optional zoomed sub-watches |
| `pitch-roll-heave` | `obc-watch` + `watchfaceLinear` | Pitch arc + roll arc + linear heave column in the band slot; single/dual scale, optional stacked readouts |
| `gauge-radial` | `instrument-radial` | Thin wrapper adding `enhanced` prop |
| `rot-sector` | `instrument-radial` | Rate of turn sector gauge |
| `azimuth-thruster` | `obc-watch` + overlay | Thruster with angle setpoint and thrust bar |

---

Expand Down Expand Up @@ -527,7 +570,8 @@ All frame/viewBox geometry is centralized in `computeRadialFrame()`:
`PADDING` comment there), pitch/roll/pitch-roll (labels inside ⇒ no
reserve; the pitch-roll composite has a coupled `buildFrame` contract),
wind-propulsion and velocity-projection-plot (explicit `padding`
override path, unchanged by design).
override path, unchanged by design). pitch-roll-heave follows pitch-roll
here: same `buildFrame` contract, same coupled zoom math.
- Compass/heading's old empirical `72 + delta(clientSize)` padding was
replaced by the analytic reserve (north arrow 16px always +
NSWE labels 16px while `showLabels`); past the reserve cap both the
Expand Down Expand Up @@ -560,13 +604,62 @@ Labels follow the design model with three placements:
- **Internal** — labels inside the ring (`tickmarksInside`).
- **Max-min** — labels at the arc ends (`endLabelsMaxMin`), e.g. the 180° gauge.

> **Validated combinations:** pitch/roll use `zoomToFitArc` + `shiftArcFrameToOuterEdge`;
> **Validated combinations:** pitch/roll/pitch-roll-heave use `zoomToFitArc` + `shiftArcFrameToOuterEdge`;
> `gauge-radial` uses per-sector `clip*` and `endLabelsMaxMin` on the 180° sector.
> Pairings like `clip*` + `zoomToFitArc` or `endLabelsMaxMin` + `zoomToFitArc` are not
> currently validated.

---

## `obc-pitch-roll-heave` geometry contract

The heave column is **not** independently sized — its dimensions fall out of the
arc geometry, and the whole layout is determined by the two half-extents
`θp` (`pitchArcAngle`, default 30) and `θr` (`rollArcAngle`, default 45):

```text
column width = bandOuterRadius - bandInnerRadius (= band thickness, 72 un-zoomed)
column height = 2 · capRadius · sin(θp) (= 184 un-zoomed)
```

`capRadius` is the radius of the pitch arc's outer edge **about its own
origin** — un-zoomed that equals the outer ring, but under zoom the arc sits on
a shifted sub-watch, so the two differ. Measuring the height against it is what
keeps the column's top and bottom edges level with the pitch arc's end caps.

Everything else follows:

| Feature | Angular span | At the defaults |
| ------------------------------- | ---------------------------- | --------------- |
| pitch arc | `[90−θp, 90+θp]` | 60°–120° |
| roll arc | `[180−θr, 180+θr]` | 135°–225° |
| heave column at the ring radius | `[270−θp, 270+θp]` | 240°–300° |
| both diagonal gaps | `90 − θp − θr` each | 15° |
| complement ring | `[270+θp, 90−θp]` through 0° | 300°→60° |

`θr` is clamped to `90 − θp` so the arcs cannot meet. **If you change the height
rule, the complement ring's endpoints move with it** — the ring starts exactly
where the column's top edge crosses the outer ring, which only coincides with
`270+θp` because of the `sin(θp)` term.

Two clearance passes run **only** under zoom, after the frames are built and
left untouched (band thickness, position and zoom level are preserved):

1. pitch vs roll at the diagonal — ratio-preserving bisection, identical to
`obc-pitch-roll`;
2. roll vs the heave column (`single-scale`) — shortens the roll arc alone,
using point-to-**rectangle** distance rather than the corner-to-corner
`signedDist` used between two arcs, because a corner can clear a rectangle
on one axis alone.

`dual-scale` instead clamps the centred column's **height only** (never its
width, which reads as a match for the band thickness) to keep
`HEAVE_CENTRE_GAP_PX` clear of the surrounding band. All three rules are
no-ops at the un-zoomed defaults, which is what reproduces the design exactly —
verify that before retuning any of the constants.

---

## Checklist for Adding New Features

- [ ] Is the feature visual/rendering? → Add to `watch.ts`
Expand Down
Loading
Loading