featherbit UI
    Preparing search index...

    Module varSuggestions

    Trace-derived $var suggestion engine.

    Feeds the node-editor's variable autocomplete popover and the var legend. Data flow: the VarEntry catalog (GET /api/vars, static — one per gateway process) describes every name/family resolve() accepts, but carries no live values; those come from the most recent debug trace for the node's policy. useContextSuggestions stitches the two together: it loads the catalog once (module-level cache), looks up the predecessor node's ContextSnapshot within the latest trace, and calls buildSuggestions to produce concrete, previewable rows.

    Exported signatures (kept in sync with this comment — update both if either drifts):

    function insertionText(name: string): string;
    function previewValue(raw: string): string;
    function predecessorSnapshot(trace: TraceDetail, predecessorId: string | null): ContextSnapshot | null;
    function bodyText(trace: TraceDetail, stepIndex: number, which: 'request' | 'response'): string | null;
    function buildSuggestions(
    catalog: VarEntry[],
    snapshot: ContextSnapshot | null,
    capturedBodies: boolean,
    requestBodyText?: string | null,
    responseBodyText?: string | null,
    ): Suggestion[];
    function useContextSuggestions(args: {
    policyName: string | null;
    nodeId: string | null;
    predecessorId: string | null | undefined;
    kind: 'policy' | 'supernode';
    debugEnabled: boolean;
    captureBodies: boolean;
    }): { suggestions: Suggestion[]; availability: Availability; catalog: VarEntry[] };

    buildSuggestions was deliberately given optional requestBodyText / responseBodyText parameters rather than resolving body text itself from a step index — useContextSuggestions is the only caller that has a trace + step index to walk, so it computes the texts via bodyText and passes them in; buildSuggestions stays a pure function of a single snapshot plus the two already-resolved strings.

    predecessorId uses an undefined-vs-null encoding chosen by the caller (the node editor, in Task 5/6): undefined means "this node has no incoming edge yet" (nothing to preview from), null means "the incoming edge comes from the listener" (preview from trace.initial, there being no prior node step), and a string is a real predecessor node id.

    headerFamily (backing both http_* and sent_http_*) skips-with-a-note any captured header whose name contains _: the suggested var name is built by lowercasing and mapping - to _ (X-Trace-Idhttp_x_trace_id), but resolve() on the gateway side maps _ back to - when it looks the header up (src/vars/mod.rs), so a header that had a literal _ in it (e.g. X_Trace_Id) is not the header that $http_x_trace_id actually resolves — it would look up x-trace-id instead and most likely miss. The chosen fix is a per-row note ("header name contains '' — not resolvable via $http*") rather than omitting the row outright, so the header is still visible in the popover/legend but is never shown with a (misleading) live value. That guard is specific to the legacy $http_* mangling and does not apply to the {{request.headers.<h>}} universal-template form below — dashes (and underscores) are legal characters in that syntax, so a header name is used there exactly as captured, with no mangling and therefore nothing to warn about.

    Since Task 8, every row built here is tagged Suggestion.trigger: 'dollar' for the legacy $name/${name} completions (unchanged from before), or 'brace' for the new {{path}} completions keyed off each catalog entry's path (entries with an empty path — no direct template equivalent, e.g. protocol, post_arg_* — simply produce no 'brace' row). Both families are built into the same flat list rather than two separate hook calls or two separate lists, because useContextSuggestions has exactly one source of truth (one catalog fetch, one trace snapshot) and a single field only ever wants one trigger's rows at a time — so the cheapest place to split them is at render time, in VarInput, by filtering on trigger for whichever token ($/${ vs {{) the user is actually completing. This also means a 'brace' and a 'dollar' row can legitimately share the same name string coincidence without colliding (they never render side by side), and callers that only ever want one family (like the var legend, keyed by the catalog's legacy name) are unaffected by the other family's presence in the list. 'brace' rows for two different static catalog entries that happen to share a path (e.g. uri/request_uri both — for now — mapping to request.path) are de-duplicated by insert text in buildSuggestions, since two rows that insert byte-identical text would otherwise be indistinguishable clutter in the popover.

    A new 'env' group (fed by GET /api/env-vars via useContextSuggestions, module-cached the same way as the catalog) is 'brace'-only: {{env.NAME}} has no $-syntax equivalent (the {{ token is still what opens the popover on an 'env-only' field), so every env.* row is tagged 'brace' and carries no value (env-var values are never fetched or shown — only names). It does, however, carry a second insertion form, insertEnvOnly (${NAME}), that VarInput swaps in for 'env-only' fields — see Suggestion.insertEnvOnly's doc comment for why {{env.NAME}} itself would never actually resolve there.

    VarInput's new templateMode prop ('full' | 'env-only' | 'none', default 'full') is a second, orthogonal filter applied on top of trigger: Task 9 decides which fields pass a non-default mode, but the filtering itself lives in VarInput now so one hook instance/suggestion list can serve every field regardless of its mode.

    Suggestion
    Availability
    AVAILABILITY_MESSAGE
    bodyText
    buildSuggestions
    insertionText
    predecessorSnapshot
    previewValue
    useContextSuggestions