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
  • Message — GetCategory(), 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.

Tip

On window desktop application (WPF), consider use textarea instead of datagrid to MessageTable for better performance. Use padding to show the different columns. And use the font in the textarea that with consistent width.

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