Table of Contents

Session Message Panel

Session messages are partitioned by kind into four sinks on LocalProjectService (obtained via dependency injection):

The panel surfaces each sink in its own tab, so one tab holds one message kind. ResetRuntime — which a project change runs — empties the first three, so each of them holds the current project only; the manipulation sink is outside that sweep, cleared only at the start of the conversion or optimization run that fills it, and its rows therefore survive a project change.

Layout

  • Tab Bar (each tab carries a floating count badge, drawn only while its sink holds messages)
    • Shell Tab
    • NC Diagnostics Tab
    • Step Diagnostics Tab
    • NC Manipulation Tab
  • Per-Tab Content
    • Filter Toolbar
      • Severity Filter Dropdown
      • Category Filter Dropdown
      • Message Text Filter Input
      • Reset Button (this tab's three filters, nothing else)
      • Export Button
      • Matched / Total Badge (also the tab's hub connection indicator)
    • Message Table

Message Table (per tab)

Each tab renders its sink's message list — Messages, Messages, or Diagnostics for the two NC-diagnostic sinks — as rows of:

  • Severity (colour-coded via GetSeverity())
  • Anchor — the kind-specific position: none for shell messages, the NC sentence ordinal (Sn <n>) for both the play-time and the manipulation NC diagnostics, and the motion step with its sentence ordinal (S<step> · Sn <n>) for step diagnostics; an NC diagnostic raised at pipeline level rather than at a source block carries none
  • MessageGetCategory(), GetId() and GetNotification()

Only take the last filtered elements (e.g. 500–1000) for user experience. Find the usage example in the code:

internal static void DemoUseSessionMessageHost(LocalProjectService localProjectService)
{
    // Session messages are partitioned by kind into three sinks on LocalProjectService:
    // - ShellProgress: session-level routine / lifecycle messages
    //   (session-scoped: null outside BeginSession/EndSession).
    // - NcDiagnosticProgress: NC-pipeline diagnostics, anchored to the NC source sentence.
    // - StepDiagnosticProgress: diagnostics anchored to a motion step.

    ShellProgress shellProgress = localProjectService.ShellProgress;
    List<IMessage> shellMessages = shellProgress == null
        ? new List<IMessage>() : shellProgress.Messages.ToList();
    foreach (IMessage message in shellMessages)
        Console.WriteLine(
            $"Shell [{message.GetSeverity()}] {message.GetId()}: {message.GetNotification()}");

    foreach (NcDiagnostic diagnostic in
        localProjectService.NcDiagnosticProgress.Diagnostics.ToList())
    {
        var ncLine = diagnostic.SentenceCarrier?.GetSentence()?.FirstIndexedFileLine;
        Console.WriteLine(
            $"NC [{diagnostic.GetSeverity()}] {diagnostic.GetId()}: {diagnostic.GetNotification()}; " +
            $"File: {ncLine?.FilePath}; LineNo: {ncLine?.GetLineNo()}; NC: {ncLine?.Line}");
    }

    foreach (StepDiagnostic diagnostic in
        localProjectService.StepDiagnosticProgress.Messages.ToList())
        Console.WriteLine(
            $"Step {diagnostic.StepIndex} [{diagnostic.GetSeverity()}] " +
            $"{diagnostic.GetId()}: {diagnostic.GetNotification()}");

    File.WriteAllLines("output-session-messages.txt",
        shellMessages.Select(m =>
        $"[{m.GetSeverity()}] {m.GetId()}: {m.GetNotification()}"));
}

Add the update-table event per sink: MessageAdded / MessageAdded, and for the session-scoped shell sink the app-lifetime bridge OnShellMessageAdded (with the matching Cleared events). The updating process has to be called by Loose Manner for user experience.

Note

The message display should be real-time.

Behavior of Export Button

Every tab has its own Export, and it carries that tab's sink alone. It writes the rows the tab is holding — the recent window pulled from that sink, narrowed by that tab's severity, category and text filters — as a CSV of Index,Count,Severity,Category,Id,Anchor,Notification,Detail, named after the sink it came from. It is disabled while the filters match nothing.

SignalR Implementation (Webapi Only)

One hub per sink — /shellMessageHub, /ncDiagnosticHub, /stepDiagnosticHub, /ncManipulationDiagnosticHub — each with a GetMessages(int limit) pull returning MessagesUpdate { messages, totalCount }. A per-sink broadcast service subscribes its sink's MessageAdded/Cleared and raises a coalesced MessagesChanged notification via LooseRunner; the client re-pulls the recent window on each notification (loss-free regardless of how many appends coalesced). The client components connect to the hubs they display to receive real-time updates.

Source Code Path

See HiNC App Anatomy for git repository links.

  • wwwroot-src/src/components/execution/SessionMessagePanel.vue (tabbed panel)
  • wwwroot-src/src/components/execution/SessionMessageTab.vue + MessageRow.vue (per-tab list)
  • wwwroot-src/src/composables/useSessionSinkHub.ts (the four hub composables)
  • Execution/SessionSinkHub.cs (the SignalR hub base; one hub per sink)
  • Execution/SessionSinkBroadcastService.cs + ShellMessageBroadcastService.cs, NcDiagnosticBroadcastService.cs, StepDiagnosticBroadcastService.cs, NcManipulationDiagnosticBroadcastService.cs (subscribe-and-rebroadcast)
  • Execution/SessionSinkDtos.cs (typed DTOs)

See Also