| task | PipeHat endpoint profiles -- the profile owns the connection |
|---|---|
| project | PipeHat |
| effort | E3 |
| effort_source | classifier |
| phase | verify |
| progress | 89/137 |
| mode | interactive |
| started | 2026-08-25 13:12:03 UTC |
| updated | 2026-09-15 00:00:00 UTC |
Three defects in the parser core, all confirmed by a CLI harness linking the real HL7Lexer.cpp
(build/repro/Repro.exe):
-
Hover tooltips stop at field depth.
HL7Lexer::getFieldIndexAtPositioncounts field separators only. HoveringROCHESTERinsideNK1|1|DOE^JANE^Q|SPO|123 MAIN ST^APT 4^ROCHESTER^NY^14624returns field index4, so the tooltip readsNK1-4. The reader has to count carets by hand to learn they are onNK1-4.3, which is the whole reason they hovered. -
Four-character Z segments are invisible to the lexer.
extractSegmentIDrequires the field separator at offset 3, soZQRY|...returns an empty ID. Empty ID means: noSCE_HL7_SEGMENT_IDtoken, so no bold blue header; no tree node; no PHI-scrub coverage; no conformance check. A whole segment silently drops out of every downstream consumer, and the only visible symptom is that it is not blue. -
A line break inside a segment is silent. Copying a message out of a Teams chat wrapped a
PV1across two lines. Nothing in PipeHat said so, no colour change and no marker, and the message went over MLLP in that state and broke the transmission.cmdValidatedoes report it, but only on demand and only as the misleadingInvalid segment ID 'ICU^101^A'.
Hovering any character in a message names exactly where you are, NK1-4[2].3.1, with the component's
name from its HL7 data type and not just the field's. Every segment a site actually sends, including
four-character Z segments, renders as a segment. And a message damaged by a chat-client paste
announces itself the moment it lands in the buffer: a coloured line you cannot miss, a one-keystroke
repair, and a refusal to put it on the wire. No more silent failure at the receiver.
Component-level PHI scrubbing, tree-view component nodes, and conformance rules addressed at component depth are not part of this work. The path resolver is the prerequisite for all three, and they are follow-on tasks. A complete HL7 component dictionary for every v2.x data type is not included; only the composite types that actually carry the fields people hover are tabled. Automatic repair of wrapped segments is not applied silently; the repair is always an explicit user action. Segment IDs longer than four characters are not accepted, since no HL7 dialect uses them and accepting them would reopen the prose-parsing hole. Batch and file envelope semantics, MLLP framing, and the settings GUI are untouched.
- The parser is the substrate. Styling, tree, scrub, conformance and validation are all views of one tokenization. A defect in the lexer is never a display bug; fixing it in a view is fixing a symptom.
- Silence is the dangerous failure. A segment that is merely not blue, or a message that merely fails at the far end, gives the operator nothing to act on. Damage must be visible where the operator is already looking.
- Permissive on input, strict on claims. Accept the dialects sites actually send; never report "clean" or "valid" about something that was skipped.
- Native C++17, MSVC, Windows-only, Unicode, x64. No new third-party dependencies.
- New pure-logic modules are header-only so
CMakeLists.txtneeds no edit. This is the established pattern forTriggerEventDB.h,HL7Escape.h,Validator.h,MessageIndex.h. HL7Lexer::extractSegmentIDis the single derivation of "what segment is this line";isSegmentStartis already defined in terms of it and must stay that way. The one deliberate exception israwSegmentIDinmain.cpp, the independent scrub-coverage safety net, which must remain independent and permissive.- The prose-rejection guard is load-bearing: three uppercase characters not followed by the field separator must not parse as a segment, or the plugin auto-activates on ordinary text.
getFieldIndexAtPositionkeeps its exact current signature and semantics. The tree view, PHI scrub, conformance check and caret-field highlight all call it.- Scintilla style indices are a contiguous small range; a new style takes the next free index in
PluginDefs.h.
HL7Lexer resolves a character position to a full HL7 path (field, repetition, component,
subcomponent) and a variable-length segment ID covering three-character IDs and four-character
Z segments; the tooltip renders that path with the component's name from its data type; and a
segment split across lines is styled as damaged, reported precisely by the validator, repairable
from the menu, and blocked from being sent over MLLP without confirmation.
- ISC-1:
HL7Lexer.hdeclares anHL7FieldPathstruct withfield,repeat,component,subcomponentmembers - ISC-2:
HL7Lexer::getPathAtPositionis declared and defined - ISC-3: Hovering
ROCHESTERin the NK1 fixture yieldsfield == 4 - ISC-4: The same probe yields
component == 3 - ISC-5: The same probe yields
subcomponent == 0(no subcomponent separator present) - ISC-6: A position inside a field with no component separator yields
component == 0 - ISC-7: A position in the second repetition of a
~-repeated field yieldsrepeat == 2 - ISC-8: A position inside a
&-delimited subcomponent yieldssubcomponent >= 1 - ISC-9: A position in the segment-ID region yields
field == 0 - ISC-10: On an MSH line, the first value after the encoding characters resolves to
field == 3 - ISC-11:
getFieldIndexAtPositionreturnsgetPathAtPosition(...).fieldfor every fixture position tested - ISC-12: Anti: no existing caller of
getFieldIndexAtPositionchanges behaviour,SegmentIDTest.exeexits 0
- ISC-13: A hover on
NK1-4component 3 renders the literal stringNK1-4.3in the calltip - ISC-14: A hover on a field with no components renders
NK1-3, with no trailing.1 - ISC-15: A hover in repetition 2 renders bracket notation
PID-3[2] - ISC-16: A hover on a subcomponent renders three-level notation
PID-3.4.1 - ISC-17:
HL7DataTypes.hmaps composite data types to ordered component names - ISC-18: The
XADtable names component 3City - ISC-19: A hover on
NK1-4.3renders the component nameCityalongside the field name - ISC-20: A data type with no component table renders the path without a component name and does not crash
- ISC-21:
extractSegmentIDreturnsZQRYfor a line beginningZQRY| - ISC-22:
extractSegmentIDreturnsZQRYfor a line that is exactlyZQRY - ISC-23:
extractSegmentIDstill returnsZPDforZPD|... - ISC-24: Anti:
extractSegmentIDreturns empty for the prose lineZONE OF INTEREST - ISC-25: Anti:
extractSegmentIDreturns empty forTHE QUICK BROWN FOX - ISC-26: Anti: a four-character non-Z run followed by the field separator (
OBXA|) returns empty - ISC-27:
tokenizeemits aSEGMENT_IDtoken of length 4 forZQRY| - ISC-28:
getFieldIndexAtPositionreturns 1 for the first value on aZQRY|line - ISC-29:
hl7val::validSegIdacceptsZQRYand still rejectsICU^101^A - ISC-30:
SegmentDBlookup miss on aZ-prefixed ID renders "site-defined Z segment", not "Unknown segment"
- ISC-31: A
continuationLinesdetector reports line 2 of aPV1wrapped across two lines - ISC-32: Anti: the detector reports nothing for a well-formed multi-segment message
- ISC-33: Anti: the detector does not flag blank lines between messages
- ISC-34:
PluginDefs.hdefinesSCE_HL7_CONTINUATIONat the next free style index - ISC-35:
ScintillaStyler::styleRangeassignsSCE_HL7_CONTINUATIONto a continuation line's bytes - ISC-36:
hl7val::validateemits a finding naming the wrapped segment, replacingInvalid segment ID - ISC-37: A
Join Wrapped Segmentsmenu command is registered ingetFuncsArray - ISC-38:
cmdMllpSendandcmdMllpReplaywarn and require confirmation when continuation lines exist
- ISC-39:
src/EndpointProfile.hexists and builds standalone with no Windows headers - ISC-40: A profile file with no section headers parses entirely as rules (backward compatibility)
- ISC-41:
[Profile]parses application, engine, messageType, displayName, description, inherits - ISC-42:
environmentacceptsqa,Dev,PRODcase-insensitively - ISC-43: Anti:
environment = Developmentis NOT read asDEV; it warns and stays Unspecified - ISC-44:
environmentRankorders Local < DEV < QA < PROD, Unspecified last - ISC-45:
[Connection]parses host, sendPort, listenPort, bindAddr, allowNonLoopback - ISC-46: A non-numeric or out-of-range port falls back to the default, never to a wild value
- ISC-47: An unparseable
allowNonLoopbackfails closed - ISC-48: Anti:
enabledin a[Connection]section is refused and warned about - ISC-49: Anti:
saveReceivedin a[Connection]section is refused and warned about - ISC-50: Anti: a rule line inside an unrecognised section does NOT become active
- ISC-51:
mergeconcatenates parent rules before child rules so the child's attribute wins - ISC-52:
mergeinherits a facet only where the child left it blank - ISC-53: Anti:
mergenever inheritsallowNonLoopbackfrom the parent - ISC-54: Anti: a child with no
[Connection]resolves to loopback, not to its parent's address - ISC-55: Anti:
mergedoes not inherit the parent'sdisplayName - ISC-56:
resolvefollows a two-level chain and clearsinheritsonce resolved - ISC-57:
resolvedetects an inheritance cycle and still returns a usable profile - ISC-58:
resolvecaps chain depth atkMaxInheritDepthand reports it - ISC-59:
resolvereports a missing parent and still loads the child - ISC-60:
resolveon a missing profile returns loopback defaults, never invented values - ISC-61:
displayNamederivesMeditech DFT > IRIS (QA)from facets - ISC-62: An explicit
displayNamewins verbatim over the derivation - ISC-63: With no facets the slug is the display name, and an empty slug reads
Default - ISC-64: Anti:
requiresExtraConfirmreturns true only for PROD and never suppresses a confirm - ISC-65: Anti: no function in
EndpointProfile.hletsenvironmentskip a confirmation - ISC-66:
serializewrites a hand-typedqaback as canonicalQA - ISC-67:
serializeround-trips every facet and connection field with no warnings - ISC-68: Anti: a profile with no connection does not gain an empty
[Connection]on save - ISC-69:
SettingsDialogsaves facets and connection alongside rules without dropping sections - ISC-70: The settings dialog shows the derived display name live as facets are edited
- ISC-71: The inherits dropdown excludes the current profile, so self-inheritance needs hand-editing
- ISC-72: Anti: switching the active profile stops a running MLLP listener
- ISC-73: The cleartext-PHI confirmation is cached per profile slug per session, not globally
- ISC-74: A PROD profile asks one extra confirmation before send or listen
- ISC-75:
Switch Endpoint Profileappears in the menu and groups entries by application/engine/type - ISC-76: Migration writes the old global
[MLLP]values into a profile that has no[Connection] - ISC-77: Anti: migration does not re-run against a profile that already has a
[Connection] - ISC-78: The full plugin compiles with zero errors on MSVC
- ISC-79: Anti: the migration runs at most once per session, never on a profile switch
- ISC-80: Anti: switching to a connection-less profile does not inherit the outgoing host/ports
- ISC-81: Editing the active profile's listenPort or bindAddr in Settings stops the listener
- ISC-82: Anti: browsing the profile dropdown does not write a
[Connection]into a visited file - ISC-83: The derived-name preview matches the picker label for a profile that inherits
- ISC-84: Anti: no control in IDD_SETTINGS overlaps another or falls outside its groupbox
- ISC-85:
src/FieldTree.hexists and builds standalone with no Windows headers - ISC-86: A field with three repetitions produces three
[n]nodes - ISC-87: Anti: a field with one value emits NO
[1]node - ISC-88: Components are named from the field's data type via
hl7dt::componentName - ISC-89: An untabled or empty data type renders the numeric path alone, never a guess
- ISC-90: Anti: an empty middle component keeps its position and does not renumber the rest
- ISC-91: Anti: a trailing empty component is not dropped
- ISC-92: Subcomponents render as
.n.m - ISC-93: A lone component carrying subcomponents still expands as component 1
- ISC-94: Anti: an escaped
\S\or\R\does not create a component or repetition - ISC-95: Splitting honours the message's own delimiters, not the defaults
- ISC-96: Anti: one runaway field is capped at
kMaxSiblingswith a summary node - ISC-97: A long value is clipped in the label while the node keeps the full value
- ISC-98: Anti: no component value contains a separator turned into a space
- ISC-99:
MessageTreeView::refreshslices raw field text between FIELD_SEP tokens - ISC-100: Anti: MSH-2 is listed as a field but never split into components
- ISC-101: Anti: the last field on a line carries no trailing CR into its label
- ISC-102: Anti: clicking the last field of a segment navigates to that segment's line
- ISC-103: Anti: a 480-message batch stops expanding at
valueNodeBudgetrather than stalling
- ISC-104: Settings is two windows: Profile Settings and Plug-in Settings
- ISC-105: Anti: the profile window contains no control that writes a global switch
- ISC-106: Anti: changing the profile dropdown writes nothing to disk
- ISC-107: Leaving a profile with unsaved edits prompts Save / Discard / Cancel
- ISC-108: Cancel on that prompt restores the combo to the profile actually loaded
- ISC-109: Cancel on the dialog with unsaved edits asks before discarding
- ISC-110: Delete names the profile and requires confirmation
- ISC-111: Anti: Delete refuses to remove the default profile
- ISC-112: Delete warns and lists profiles that inherit from the target
- ISC-113: Deleting the active profile falls back to the default
- ISC-114: New runs a seven-step wizard and writes nothing until the last step
- ISC-115: Anti: the wizard rejects a profile name containing a dot
- ISC-116: Anti: the wizard rejects a name that already exists
- ISC-117: The profile window states whether the global non-loopback permission is on
- ISC-118: The Plug-in Settings button opens that window and the state line refreshes
- ISC-119: Clearing the global permission stops a running listener
- ISC-120: Tooltips explain application, engine, environment, inherits and the bind opt-in
- ISC-121:
hl7tree::Nodecarries an offset and length relative to the field text - ISC-122: Slicing the field by a repetition's range returns that repetition
- ISC-123: Slicing by a component's range returns that component
- ISC-124: Slicing by a subcomponent's range returns that subcomponent
- ISC-125: An empty component reports zero length and its would-be start position
- ISC-126: Anti: the truncation summary node claims no range (offset -1)
- ISC-127: Clicking a component node selects exactly that component in the editor
- ISC-128: Clicking a subcomponent node selects exactly that subcomponent
- ISC-129: Clicking a field node selects the whole field
- ISC-130: Clicking a segment node selects the whole line
- ISC-131: Anti: selection stays correct on a line containing a non-ASCII character
- ISC-132: Clicking a node inside a folded segment unfolds it first
- ISC-133: A single left click selects the node's piece in the editor
- ISC-134: Arrow-key walking the tree moves the selection with it
- ISC-135: Anti: a single click does not move keyboard focus out of the tree
- ISC-136: A double click selects the piece and moves focus to the editor
- ISC-137: Anti: rebuilding the tree does not scroll the editor
| isc | type | check | threshold | tool |
|---|---|---|---|---|
| ISC-1..2 | static | declarations present in header | exact match | grep |
| ISC-3..11 | unit | path resolver against fixtures | exact integer equality | tests/FieldPathTest.exe |
| ISC-12 | regression | existing segment-ID and PHI harness | exit code 0 | tests/SegmentIDTest.exe |
| ISC-13..16 | unit | rendered path string | exact string equality | tests/FieldPathTest.exe |
| ISC-17..20 | unit | component-name lookup | exact string equality | tests/FieldPathTest.exe |
| ISC-21..29 | unit | segment-ID and tokenizer behaviour | exact equality | tests/SegmentIDTest.exe |
| ISC-30 | static | tooltip branch text | string present | grep |
| ISC-31..33 | unit | continuation detector over line vectors | exact line-number set | tests/FieldPathTest.exe |
| ISC-34 | static | style index defined, no collision | unique value | grep |
| ISC-35 | static | style assignment in styleRange | branch present | grep |
| ISC-36 | unit | validator finding text | substring match | tests/FieldPathTest.exe |
| ISC-37..38 | static | menu registration and send guard | branch present | grep |
| ISC-39..68 | unit | endpoint profile parse/merge/resolve/serialize | 104 assertions, exit 0 | tests/EndpointProfileTest.exe |
| ISC-69..71 | manual | settings dialog round-trip on a real profile file | sections survive a save | Notepad++ debug session |
| ISC-72..74 | manual | switch with listener up, PROD send, repeat switch | listener stops, confirms fire | Notepad++ debug session |
| ISC-75 | static | menu registration present | branch present | grep |
| ISC-79 | static | session static bool gate present in the migration |
branch present | grep |
| ISC-80..83 | manual | switch to a legacy profile, edit port in place, browse the dropdown | file unchanged, listener stopped, label matches | Notepad++ debug session |
| ISC-84 | static | rectangle sweep over every IDD_SETTINGS control | zero overlaps, zero out of bounds | layout checker script |
| ISC-85..98 | unit | field splitting, naming, caps, escapes | 49 assertions, exit 0 | tests/FieldTreeTest.exe |
| ISC-99..103 | manual | open a PID with repeats, an MSH, and a 480-message batch | subtree correct, no stall | Notepad++ debug session |
| ISC-104..105 | static | control inventory per dialog template | no global control in IDD_SETTINGS | rg over resource.rc |
| ISC-106..119 | manual | browse, edit, switch, cancel, delete, create | prompts fire, nothing written early | Notepad++ debug session |
| ISC-120 | manual | hover each endpoint field | tooltip appears and wraps | Notepad++ debug session |
| ISC-121..126 | unit | ranges re-slice the source field | exact string equality | tests/FieldTreeTest.exe |
| ISC-127..132 | manual | click each node depth in the panel | selection matches the node | Notepad++ debug session |
| ISC-133..137 | manual | single click, arrow keys, double click, reopen a buffer | selection follows, focus stays, no scroll on rebuild | Notepad++ debug session |
| ISC-76..77 | manual | upgrade over an existing PipeHat.ini, then restart twice | written once, not twice | Notepad++ debug session |
| all | build | full plugin compiles | zero errors | cmake --build build --config Release |
| all | build | both standalone tests under MSVC | exit 0, /W4 clean | cmd /c tests\runtests.bat |
| name | description | satisfies | depends_on | parallelizable |
|---|---|---|---|---|
| path-resolver | HL7FieldPath and getPathAtPosition in HL7Lexer, getFieldIndexAtPosition delegates |
ISC-1..12 | none | yes |
| datatype-components | HL7DataTypes.h component-name tables for common composite types |
ISC-17..18, ISC-20 | none | yes |
| tooltip-render | Path and component-name formatting in ScintillaStyler::showFieldTooltip |
ISC-13..16, ISC-19..20, ISC-30 | path-resolver, datatype-components | no |
| variable-segid | Variable-length segment IDs (3, or Z-prefixed 4) across lexer, validator, tokenizer | ISC-21..29 | none | yes |
| wrap-detect | Continuation-line detector plus validator finding | ISC-31..33, ISC-36 | variable-segid | no |
| wrap-alert | SCE_HL7_CONTINUATION style, join command, MLLP send guard |
ISC-34..35, ISC-37..38 | wrap-detect | no |
| endpoint-core | EndpointProfile.h: sections, facets, environment enum, merge, resolve, serialize |
ISC-39..68 | none | yes |
| endpoint-dialog | Settings dialog endpoint section, derived-name preview, inherits picker | ISC-69..71 | endpoint-core | no |
| endpoint-glue | main.cpp load/resolve/migrate, per-profile confirm, switch command |
ISC-72..78 | endpoint-core | no |
-
2026-08-25T13:12:03Z Both reported defects reproduced with a CLI harness linking the real
HL7Lexer.cppbefore any code was read for a fix. Evidence:segId=""forZQRY|1|abc, andfield index 4for a hover on the third component ofNK1-4. -
2026-08-25T13:12:03Z Root-cause-at-ingestion: all three symptoms enter at the lexer, not at the display. Fixing the tooltip alone would leave the tree, scrub and conformance views blind to components; fixing the styler alone would leave
ZQRYout of the tree and out of PHI coverage. Both fixes go inHL7Lexer. -
2026-08-25T13:12:03Z Four-character acceptance is restricted to
Z-prefixed IDs. Accepting any four-character run followed by the field separator would widen the prose-parsing hole the existing delimiter guard was added to close. -
2026-08-25T13:12:03Z
getFieldIndexAtPositionis kept and reimplemented as a thin wrapper overgetPathAtPositionrather than replaced, so the four existing callers cannot silently drift from the new resolver. Same single-derivation rule thatisSegmentStartalready follows. -
2026-08-25T14:05:00Z Wrapped-segment detection has one derivation (
hl7val::continuationLines), run perMessageSpanwith that message's own delimiters. The styler, the hover warning, the validator, the join command and the two MLLP guards all reduce to it, so the editor cannot show damage the send path stays quiet about. -
2026-08-25T14:05:00Z The join deletes the line break and inserts nothing. The wrap lands mid-value, so inserting any character would corrupt the data the repair exists to rescue.
-
2026-08-25T14:05:00Z The MLLP guard warns and defaults to No rather than repairing silently. Editing a message on its way to a live interface without being asked is worse than sending a known-bad one, and the plugin cannot tell a chat-client wrap from a deliberate test fixture.
-
2026-09-15T00:00:00Z Connection settings move into the profile;
enabledandsaveReceiveddeliberately stay global inPipeHat.ini. Both are switches a profile change would otherwise flip as a side effect of a menu click -- one starts networking, the other starts writing cleartext PHI to disk.endpoint::parserefuses them inside a[Connection]section rather than ignoring them, so a hand-edited file says why it did not work. -
2026-09-15T00:00:00Z
environmentis a label, not a gate. Deconstructing what actually decides whether a bind is safe givesallowNonLoopbackplusbindAddr; the environment string has no causal relationship to the socket. It may therefore only ADD friction. There is norequiresLessConfirmation()and there must never be one, or a mistyped label becomes a bypass. -
2026-09-15T00:00:00Z The cleartext-PHI acknowledgement is keyed per profile slug per session. A single session-wide bool stops describing the endpoint once switching is cheap; re-firing on every switch trains the user to click through it. Per profile per session is the only version that still carries information when the profile count grows.
-
2026-09-15T00:00:00Z A
[Connection]is never inherited. Inheriting one would mean addinginherits = <parent with allowNonLoopback>silently widens where a child binds, which makes inheritance a privilege path. Rules inherit; the address does not. -
2026-09-15T00:00:00Z Migration of the old global
[MLLP]block is made idempotent by construction -- it only writes a profile with no[Connection], and writing one removes that condition. An ini flag guarding it would re-run and overwrite a hand-edited profile the first time the flag was lost. -
2026-09-15T00:00:00Z
refined:the display name derives from facets rather than from the filename, so renaming costs no file operation and two profiles may share a label. The slug stays the key. -
2026-09-15T00:00:00Z Show your math on the E3 delegation floor (2, soft; 1 used): Forge was invoked to read the Win32 changes that cannot be compiled on this host. A second Claude-family reviewer was not spawned because
codexis absent here, so Forge already degrades to the same model family -- a second one returns correlated opinions rather than independent ones. The real independent check is the MSVC build on the work PC, tracked as ISC-78. -
2026-09-15T00:00:00Z
refuted:the migration was believed safe because it only writes a profile with no[Connection]. That is true but insufficient.loadProfilealso runs on every switch, and thereg_mllpholds the OUTGOING profile's connection, so the guard let the previous endpoint's host and ports be written permanently into any connection-less profile -- and becauserequiresExtraConfirmreads the NEW profile's environment, a switch from PROD to a legacy profile would keep the production address while dropping the production prompt. Now gated by a session-scopedstatic boolso it happens at startup only. The call was not deleted: the startup load is the entire upgrade path for a user withAllowNonLoopback=1in the ini. -
2026-09-15T00:00:00Z
refined:stopping the listener on a profile-NAME change is not enough. An in-place edit of the active profile'slistenPort, or clearing the global opt-in, moves the socket without renaming anything. The comparison is now onlistenPortandeffectiveBindAddrtaken across the dialog and evaluated afterloadProfile, so it sees the final ANDed values. -
2026-09-15T00:00:00Z
refined:readEndpointno longer setshasConnectionunconditionally. Browsing the profile dropdown calls it on every combo change, which silently rewrote every file the user merely looked at. Ag_connDirtyflag, suppressed whilepopulateEndpointruns becauseSetDlgItemTextWfiresEN_CHANGEexactly like a keystroke, marks a real edit. -
2026-09-15T00:00:00Z Tree depth below the field came from a LinkedIn comment asking for a repetition sub-tree "like hl7inspector.com". Built as components and subcomponents too, not repetitions alone: hl7inspector shows the full depth, and a repetition level whose children are still flat joined text would answer the question halfway.
-
2026-09-15T00:00:00Z
refuted:the field text shown in the tree was believed to be the field. It was the lexer's FIELD_VALUE tokens joined with spaces, so every separator was discarded andDOE^JANE^Qreached the panel asDOE JANE Q.refreshnow slices the raw line between FIELD_SEP tokens.criterion_now:ISC-98 asserts no component value contains a separator-turned-space. -
2026-09-15T00:00:00Z Found while rewriting the loop: the end-of-line field emit passed
(LPARAM)(fieldIdx)where every other node passesline + 1, so clicking the last field of a segment navigated to whichever line shared that number. The two emit paths are now one lambda so they cannot drift again. -
2026-09-16T00:00:00Z The build box is
dell, not the work PC. The work PC has no C++ toolchain at all -- no Visual Studio, no vswhere, no cmake -- so it can run PipeHat but cannot produce it.dellcarries VS 2022 BuildTools, cmake on PATH, and the originalC:\opencode\hl7-npp-plugintree.tests/runtests.batwas added so the MSVC half of the test run is one command rather than a PowerShell quoting exercise. -
2026-09-17T00:00:00Z Tree nodes carry a document range rather than a line number. A node used to store only
line + 1, so clickingOBX-5.3.3didSCI_GOTOLINEand left the reader on the right line with no indication which piece they had asked about.MessageTreeView::m_targetsholds a range per node andlParamindexes it, with 0 reserved for "no target" so it stays distinguishable from the first entry. -
2026-09-17T00:00:00Z Ranges are measured in
wchar_tand converted to bytes at click time against the line's own text, viaSciUtils::utf8Len. Scintilla addresses bytes; counting characters works until a message carries an accented name, and then every position after it is silently wrong.SCI_SETSELandSCI_ENSUREVISIBLEhad to be added to the stripped vendoredScintilla.h, per the standing rule for new Scintilla messages. -
2026-09-17T00:00:00Z
refuted:single-click navigation was believed missing, and the fix was believed to be adding it.TVN_SELCHANGEDWwas already handled -- with a SECOND copy of the navigation code that still readlParamasline + 1. When node lParams became indexes intom_targets, only the double-click path was updated, so single click calledSCI_GOTOLINEwith an index and jumped to unrelated lines.learned:two implementations of one behaviour is the defect; a contract change updates whichever copy you remember.criterion_now:both paths callonTreeClick, which takes only amoveFocusflag. -
2026-09-17T00:00:00Z A selection change does not move focus; a double click does. Walking the tree with the arrow keys is the case that matters when reading down a message, and stealing focus on the first keypress would end the walk.
Run 2026-08-25, Release build, MSVC BuildTools 2022, x64.
ISC-3..11, ISC-17..20, ISC-29, ISC-31..33, ISC-36: build\FieldPathTest.exe:
45 passed, 0 failed
ISC-12, ISC-21..28: build\SegmentIDTest.exe, the pre-existing lexer and PHI-map
regression harness, unchanged by these edits:
ALL PASS (0 failures)
All ISCs, build gate: cmake --build build --config Release:
PipeHat.vcxproj -> C:\opencode\hl7-npp-plugin\build\Release\PipeHat.dll
Zero warnings introduced. Artifact written 2026-08-25 09:28, 543,232 bytes.
ISC-1..2: HL7FieldPath and getPathAtPosition declared in src/HL7Lexer.h, defined
in src/HL7Lexer.cpp; getFieldIndexAtPosition is now a one-line delegation to it.
ISC-30: showFieldTooltip carries a segId[0] == L'Z' branch rendering
(site-defined Z segment) where the SegmentDB lookup misses.
ISC-34: SCE_HL7_CONTINUATION 11 in src/PluginDefs.h, one past the previous highest
style index. SCI_STYLESETEOLFILLED 2057 had to be added to the stripped vendored
include/npp/Scintilla.h.
ISC-35: ScintillaStyler::styleRange styles the whole line as SCE_HL7_CONTINUATION
and skips tokenizing it when extractSegmentID is empty and continuationOwner is not.
ISC-37: Join Wrapped Segments registered at Ctrl+Alt+Shift+J; g_funcItems grown
from 25 to 26 (the array is sized exactly).
ISC-38: confirmWrappedBeforeSend is called by both cmdMllpSend and cmdMllpReplay
before any bytes leave; it defaults to No and names the offending line numbers.
Tagged v2.3.0 (de18554) 2026-08-25. Tag-triggered workflow run 32873658912 completed
success: full Release build, all six standalone harnesses green, PipeHat-v2.3.0-x64.zip
(256,748 bytes) attached to the GitHub release.
Asset verified by extraction rather than by exit code. The zip contains
PipeHat/PipeHat.dll (540,160 bytes) plus README, CHANGELOG and LICENSE, laid out so it
unpacks straight into the Notepad++ plugins folder. The DLL is PE machine 0x8664 (x64),
exports all six Notepad++ ABI entry points (setInfo, getName, getFuncsArray,
beNotified, messageProc, isUnicode), and carries the UTF-16 literals
Join Wrapped Segments, Line break inside segment and 2.3.0, so the release build is
the feature build and not a stale artifact.
Gap noted, not fixed: the DLL has no VERSIONINFO resource, so Windows file properties show
an empty File version. The plugin reports its own version from HL7_PLUGIN_VERSION, so
nothing depends on it, but the file looks unversioned to anything that inspects it from
outside.
ISC-13..16 and ISC-19 are implemented but not yet observed in a live calltip. The
formatting sits in ScintillaStyler.cpp, which cannot be linked without the Scintilla host,
so the standalone harness verifies the resolver and the component tables that feed it but not
the rendered string. Closing them needs one pass in a Notepad++ session with the built DLL
deployed: hover the NK1 fixture city (expect NK1-4.3: Address (XAD) City), the NK1-3
field (expect NK1-3, no trailing .1), and the second PID-3 repetition (expect
PID-3[2].1). Same session closes the visual check on the pink continuation wash.