A programmer’s guide
to JSON Inspect.
Start with the system, then follow the data, state and code behind every major feature.
Guida per programmatori
di JSON Inspect.
Prima il sistema, poi dati, stato e codice dietro tutte le funzionalità principali.
01 · The application at a glance
JSON Inspect is a generic JSON explorer with firewall-oriented defaults. It runs entirely inside a desktop browser opened from index.html: no web server, build step or runtime package installation.
The browser UI manages controls, a draft search and a small visible window of records. A dedicated Web Worker owns the loaded document, flattened records, filtering, sorting and exports. Named filters and column layouts are metadata stored separately from the data file.
SRX and FortiGate labels in presets/layouts do not make this a vendor parser. A saved layout refers to field paths that must exist in the supplied JSON. The viewer does not automatically translate native CLI text or normalize unrelated schemas.
01 · Panoramica dell’applicazione
JSON Inspect è un visualizzatore JSON generico con impostazioni predefinite orientate ai firewall. Funziona interamente nel browser desktop aprendo index.html: non richiede server web, compilazione o installazione di pacchetti per l’esecuzione.
L’interfaccia gestisce controlli, ricerca in bozza e una piccola finestra di record visibili. Un Web Worker dedicato mantiene il documento caricato, i record appiattiti, i filtri, l’ordinamento e le esportazioni. Filtri salvati e layout delle colonne sono metadati separati dal file di dati.
I nomi SRX e FortiGate nei filtri/layout non rendono l’applicazione un parser specifico del produttore. Un layout usa percorsi di campo che devono esistere nel JSON fornito. Il visualizzatore non traduce automaticamente testo CLI nativo e non normalizza schemi diversi.
02 · Follow one file through the app
load(file)callsunload(), invalidates request IDs, clears Undo and terminates the old worker.startWorker()serializescreateEngineandworkerMaininto a Blob URL. This avoids fetching worker dependencies from a local-file origin.- The worker reads the file, parses it, validates depth/numbers, discovers collections and builds their flattened projection. It emits
sources, thendataset. - The UI orders fields using
preferredthen locale ordering, selects default visibility and resolves the last chosen named layout. It sends the applied query. - A current
filteredresponse commits the accepted search.requestPage()requests a page or virtual window. Rows are rendered only after the correspondingpageresponse.
Changing collection preserves the applied filter but resets sorting and pagination. Opening a new file clears the search and Undo history. Named filters, layouts and theme remain in browser storage.
02 · Il percorso di un file nell’app
load(file)richiamaunload(), invalida gli ID delle richieste, azzera Undo e termina il worker precedente.startWorker()serializzacreateEngineeworkerMainin un URL Blob. In questo modo non deve scaricare dipendenze del worker da un’origine locale.- Il worker legge il file, esegue il parsing, valida profondità e numeri, rileva le raccolte e costruisce la proiezione appiattita. Emette
sourcese poidataset. - L’interfaccia ordina i campi usando
preferrede poi l’ordinamento locale, imposta la visibilità predefinita e risolve l’ultimo layout selezionato. Invia quindi la ricerca applicata. - Una risposta
filteredancora valida conferma la ricerca.requestPage()richiede una pagina o finestra virtuale. I record vengono mostrati solo dopo la relativa rispostapage.
Cambiare raccolta mantiene il filtro applicato ma azzera ordinamento e paginazione. Aprire un nuovo file azzera ricerca e cronologia Undo. Filtri salvati, layout e tema rimangono nella memoria del browser.
03 · Source map and boot order
The app uses classic scripts, not ES modules. Top-level declarations share a global lexical environment; functions reference S, V and helpers across files. The split improves responsibility boundaries but is not dependency isolation.
| File | Responsibility |
|---|---|
core.js | Pure engine: flattening, parser, CIDR, comparison, safe metadata import. |
completions.js | Tolerant lexer and field/operator suggestions. |
worker.js | workerMain(): owns data and handles processing messages. |
filters.js / layouts.js | Executable bundled assignments containing shared default metadata. |
virtual.js | Height prefix sums and compressed scroll coordinates. |
layout-model.js | Validates, resolves and compares column layout definitions. |
state.js | S, V, request maps, shared helpers and preference access. |
worker-client.js | Lifecycle, correlation IDs, loading, cancellation and export replies. |
filter-controller.js | Draft/applied state, validated changes, Undo and header projections. |
table-view.js | Table/tree rendering, virtual window, drag/drop and column controls. |
app.js | Remaining event wiring, saved-filter UI, clipboard and startup. |
search-editor.js | DOM autocomplete controller; edits drafts without applying them. |
layout-manager.js | Named layouts UI, persistence, import/export and dataset hooks. |
index.html / styles.css | Controls, script order and visual/virtual-row geometry. |
tests/ | Node engine tests and offline Playwright browser regressions. |
core.js → completions.js → worker.js → filters.js → virtual.js
→ layout-model.js → layouts.js → state.js → worker-client.js
→ filter-controller.js → table-view.js → app.js
→ search-editor.js → layout-manager.js
app.js restores presets/theme and invokes presetOptions(), hint(), render() and updateDraftState(). The layout controller initializes afterwards; early layout callbacks therefore use optional chaining. Keep the boot order when moving functions. Helpers placed in createEngine() must remain self-contained because the factory is copied into the worker as source text.
03 · Mappa dei sorgenti e ordine di avvio
L’app usa script classici, non moduli ES. Le dichiarazioni di primo livello condividono l’ambiente lessicale globale; le funzioni accedono a S, V e alle funzioni comuni di altri file. La suddivisione separa le responsabilità ma non isola le dipendenze.
| File | Responsabilità |
|---|---|
core.js | Motore puro: appiattimento, parser, CIDR, confronto, import sicuro dei metadati. |
completions.js | Lexer tollerante e suggerimenti di campi/operatori. |
worker.js | workerMain(): mantiene i dati ed elabora i messaggi. |
filters.js / layouts.js | Assegnazioni JavaScript incluse con i metadati predefiniti condivisi. |
virtual.js | Somme cumulative delle altezze e coordinate di scorrimento compresse. |
layout-model.js | Valida, risolve e confronta le definizioni dei layout. |
state.js | S, V, mappe delle richieste, funzioni comuni e accesso alle preferenze. |
worker-client.js | Ciclo di vita, ID di correlazione, caricamento, annullamento e risposte export. |
filter-controller.js | Bozza/stato applicato, modifiche validate, Undo e proiezione sugli header. |
table-view.js | Rendering tabella/albero, finestra virtuale, drag/drop e controlli colonne. |
app.js | Collegamento degli eventi restanti, filtri salvati, appunti e avvio. |
search-editor.js | Controller DOM dell’autocompletamento; modifica la bozza senza applicarla. |
layout-manager.js | Interfaccia dei layout, persistenza, import/export e hook del dataset. |
index.html / styles.css | Controlli, ordine degli script e geometria visiva/delle righe virtuali. |
tests/ | Test del motore con Node e regressioni browser offline con Playwright. |
core.js → completions.js → worker.js → filters.js → virtual.js
→ layout-model.js → layouts.js → state.js → worker-client.js
→ filter-controller.js → table-view.js → app.js
→ search-editor.js → layout-manager.js
app.js ripristina filtri/tema e invoca presetOptions(), hint(), render() e updateDraftState(). Il controller dei layout viene inizializzato dopo; le chiamate anticipate ai suoi hook usano quindi l’optional chaining. Mantenere l’ordine di avvio quando si spostano funzioni. Le funzioni dentro createEngine() devono rimanere autonome perché la factory viene copiata nel worker come testo sorgente.
04 · Data model and field paths
The worker holds root (parsed document), rows (selected collection), flat (one flattened object per row), and matches (ordered original row indexes). Filtering changes the index list, not the raw records. Table record numbers are original indexes + 1, not current sort positions.
select(path) builds the next projection before committing it. Root arrays are direct collections; otherwise discovery follows object properties up to eight levels and stops at arrays. A direct sessions collection is preferred, then the first discovered array, then the whole document as one row.
flatten() recurses through nonempty objects. Arrays, empty objects, null and scalars remain leaf values. Flat objects have a null prototype. getField() checks own properties, including after structured cloning into the UI.
Input: {"flows":{"in":{"source_ip":"10.1.2.3"}},"a.b":7,"tags":["dns","prod"]}
Flat keys:
flows.in.source_ip → "10.1.2.3"
["a.b"] → 7
tags → ["dns", "prod"]
Expression: flows.in.source_ip IN 10.0.0.0/8 AND `["a.b"]` = 7
Scalar row: 42 → {"$": 42}
display() returns strings unchanged, missing values as an empty string and other values via JSON.stringify. Nested view and JSON export use the originals; table filtering and CSV use flattened rows. Collection selection does not merge arrays from separate branches.
04 · Modello dei dati e percorsi dei campi
Il worker mantiene root (documento interpretato), rows (raccolta scelta), flat (un oggetto appiattito per record) e matches (indici originali ordinati). I filtri modificano la lista degli indici, non i record originali. I numeri di record della tabella sono gli indici originali + 1, non le posizioni nell’ordinamento corrente.
select(path) costruisce la nuova proiezione prima di confermarla. Un array alla radice è una raccolta diretta; altrimenti il rilevamento percorre proprietà di oggetti fino a otto livelli e si ferma agli array. Viene preferita una raccolta diretta sessions, poi il primo array trovato, infine l’intero documento come singolo record.
flatten() attraversa ricorsivamente gli oggetti non vuoti. Array, oggetti vuoti, null e scalari rimangono valori foglia. Gli oggetti appiattiti hanno prototipo nullo. getField() verifica le proprietà proprie, anche dopo la clonazione strutturata verso l’interfaccia.
Input: {"flows":{"in":{"source_ip":"10.1.2.3"}},"a.b":7,"tags":["dns","prod"]}
Chiavi appiattite:
flows.in.source_ip → "10.1.2.3"
["a.b"] → 7
tags → ["dns", "prod"]
Espressione: flows.in.source_ip IN 10.0.0.0/8 AND `["a.b"]` = 7
Record scalare: 42 → {"$": 42}
display() restituisce le stringhe senza modifiche, i valori mancanti come stringa vuota e gli altri valori tramite JSON.stringify. La vista annidata e l’export JSON usano gli originali; filtri tabellari e CSV usano i record appiattiti. La scelta della raccolta non unisce array di rami diversi.
05 · State ownership and invariants
| State | Meaning |
|---|---|
S.columns / S.visible | Ordered list of all fields / Set of selected fields. |
query input + S.editMode | Current draft. May be incomplete or invalid. |
S.submitted | Latest pending query snapshot; cleared on acceptance/current query error. |
S.applied | Last query accepted by the worker; basis for chips and sorting. |
S.items / S.count / S.total | Current window / matched count / collection size. |
S.sort / S.offset / S.view | Sort descriptor / page offset / table or tree. |
V | Virtual window bounds, heights, coordinate map, observer and pending request. |
filterHistory | Up to 50 {draft, applied} snapshots; memory only. |
recordRequests / exportRequests | Correlated callbacks / export filename and operation snapshots. |
openTrees / virtualColumnWidths | Expanded paths by original index / measured widths per field and dataset. |
- The expression bar contains the complete draft filter. There is no separate hidden column-filter predicate.
- Editing a draft does not change results or exports. Sorting explicitly reuses
S.appliedand preserves unapplied text. - A view/layout change must not mutate the filter. Hiding a column must not disable its conditions.
- Every response must be checked before it can update state. A worker identity check alone is not enough for multiple requests to the same worker.
05 · Stato e invarianti
| Stato | Significato |
|---|---|
S.columns / S.visible | Elenco ordinato di tutti i campi / Set dei campi selezionati. |
query input + S.editMode | Bozza corrente. Può essere incompleta o non valida. |
S.submitted | Snapshot dell’ultima ricerca in attesa; azzerato alla conferma o all’errore corrente. |
S.applied | Ultima ricerca accettata dal worker; base per chip e ordinamento. |
S.items / S.count / S.total | Finestra corrente / conteggio corrispondenze / dimensione raccolta. |
S.sort / S.offset / S.view | Descrittore di ordinamento / offset pagina / tabella o albero. |
V | Limiti della finestra virtuale, altezze, mappa coordinate, observer e richiesta in attesa. |
filterHistory | Fino a 50 snapshot {draft, applied}; solo in memoria. |
recordRequests / exportRequests | Callback correlati / snapshot di nome file e operazione per l’export. |
openTrees / virtualColumnWidths | Percorsi espansi per indice originale / larghezze per campo e dataset. |
- La barra delle espressioni contiene il filtro completo in bozza. Non esiste un predicato separato e nascosto per i filtri di colonna.
- Modificare la bozza non cambia risultati o esportazioni. L’ordinamento riusa esplicitamente
S.appliede mantiene il testo non applicato. - Un cambio di vista/layout non deve modificare il filtro. Nascondere una colonna non deve disattivarne le condizioni.
- Ogni risposta deve essere verificata prima di aggiornare lo stato. Controllare solo l’identità del worker non basta per richieste multiple allo stesso worker.
06 · Worker protocol and race protection
post(m) supplies the current op; a caller can override it with null for background work. Replies echo id and op. Errors use {type:"error", action, message, id, op}.
| Request | Payload fields | Response | Work |
|---|---|---|---|
load | file | status, sources, dataset | Parse a newly selected file. |
source | path | dataset | Build another collection projection. |
query | query, mode, sort | filtered | Compile predicate, scan flat rows, sort matching indexes. |
page | offset, size, view | page | Table: {index, flat}; tree: {index}. Maximum size 250. |
record | index | record | Return {index, raw} on explicit inspection/expansion. |
export | format, columns | export | Return Blob + format for matched records. |
unique | field | export | Distinct displayed values, default string sort, newline-separated TXT. |
S.sourceId guards status/source/dataset responses; S.id guards filtered responses; S.pageId guards pages. Record/export maps guard their own monotonically increasing IDs. S.operation identifies foreground busy/timeout state; done(op) cannot finish a newer operation.
Virtual scroll page requests and lazy record requests use op:null. Replaced workers are rejected by identity. Exports keep the filename/operation recorded at request time, so a late download cannot rename itself or clear a newer busy state. Stale replies are ignored; most obsolete work is not cooperatively cancelled inside the worker. Long synchronous scans still occupy that worker until completion or termination.
// Main thread → worker (illustrative request)
{ type: "query", id: 12, op: 31,
query: "duration >= 3600", mode: "expression",
sort: { field: "duration", direction: -1 } }
// Worker → main thread
{ type: "filtered", id: 12, op: 31,
count: 42, total: 1000, filtered: true }06 · Protocollo worker e protezione dalle risposte obsolete
post(m) aggiunge l’op corrente; il chiamante può sovrascriverlo con null per attività in background. Le risposte riportano id e op. Gli errori usano {type:"error", action, message, id, op}.
| Richiesta | Campi del payload | Risposta | Operazione |
|---|---|---|---|
load | file | status, sources, dataset | Esegue il parsing di un nuovo file. |
source | path | dataset | Costruisce la proiezione di un’altra raccolta. |
query | query, mode, sort | filtered | Compila il predicato, cerca nei record appiattiti, ordina gli indici. |
page | offset, size, view | page | Tabella: {index, flat}; albero: {index}. Dimensione massima 250. |
record | index | record | Restituisce {index, raw} per ispezione/espansione esplicita. |
export | format, columns | export | Restituisce Blob + formato per i record filtrati. |
unique | field | export | Valori visualizzati distinti, ordinamento stringa predefinito, TXT con un valore per riga. |
S.sourceId protegge le risposte status/source/dataset; S.id quelle filtered; S.pageId le pagine. Le mappe record/export verificano i propri ID crescenti. S.operation identifica lo stato di occupato/timeout in primo piano; done(op) non può terminare un’operazione più recente.
Le richieste di pagine virtuali e dei record su richiesta usano op:null. Le risposte di worker sostituiti vengono scartate tramite controllo dell’identità. Gli export conservano nome file e operazione registrati alla richiesta: un download tardivo non cambia nome né termina lo stato di occupato di un’operazione più recente. Le risposte obsolete vengono ignorate; il lavoro obsoleto generalmente non viene annullato cooperativamente nel worker. Una scansione sincrona lunga occupa il worker fino al termine o alla sua terminazione.
// Thread principale → worker (richiesta di esempio)
{ type: "query", id: 12, op: 31,
query: "duration >= 3600", mode: "expression",
sort: { field: "duration", direction: -1 } }
// Worker → thread principale
{ type: "filtered", id: 12, op: 31,
count: 42, total: 1000, filtered: true }07 · Expression engine and jq compatibility
compile(input, mode) produces a predicate function. The expression lexer emits words, quoted literals, backtick fields, regular expressions, operators and parentheses. Recursive descent builds closures through primary(), and() and or(); there is no separate persistent AST or JavaScript eval.
| Syntax | Meaning |
|---|---|
AND / OR / NOT | Boolean composition; NOT → AND → OR precedence. |
= / == / != | Strict type-sensitive equality/inequality. |
CONTAINS / : | Case-insensitive displayed-value substring. |
> / >= / < / <= | Finite numeric comparison, also accepting numeric strings; ignores null/missing/empty/non-numeric values. |
IN | Field value belongs to a literal IPv4 or IPv6 CIDR. |
~ / =~ / !~ | Regex match / negative match; expression flags i, m, s, u. |
exists(field) | Own field is present, including a null value. |
.other.field | Right-hand field reference rather than a string literal. |
Plain text searches case-insensitive literal text across all flattened values, including hidden fields. Regex compiles the entire input with the i flag. Expression also supports bare global text, regex or CIDR searches. Arrays remain values: a global CIDR search does not recursively inspect their elements.
jq subset accepts .[] | select(expression) and feeds the inner expression to the same parser. It supports nested select(...), not jq transformations, arbitrary pipelines, array iteration or jq’s complete truth semantics.
flows.in.source_ip IN 10.0.0.0/8 AND (application ~ /dns/i OR duration >= 3600)
flows.in.source_ip != .flows.out.destination_ip
exists(source_nat_pool) AND source_nat_pool != null
.[] | select(.duration >= 3600 and .status == "active")
fieldName() emits plain paths where safe and escaped backticks for special/reserved names. expression() validates and converts each mode to a common expression. convert() refuses lossy conversions. CIDR matching uses BigInt address arithmetic; IPv4 and IPv6 families remain distinct, including IPv4-mapped IPv6 values.
terms() extracts top-level AND terms while keeping OR/NOT groups intact. mergeConditions() deduplicates identical term text, not all logically equivalent expressions. joinTerms() adds parentheses when combining terms.
07 · Motore delle espressioni e compatibilità jq
compile(input, mode) produce una funzione predicato. Il lexer emette parole, letterali tra virgolette, campi tra backtick, espressioni regolari, operatori e parentesi. Il parser ricorsivo costruisce closure tramite primary(), and() e or(); non esistono un AST persistente separato o chiamate JavaScript eval.
| Sintassi | Significato |
|---|---|
AND / OR / NOT | Composizione booleana; precedenza NOT → AND → OR. |
= / == / != | Uguaglianza/disuguaglianza stretta, sensibile al tipo. |
CONTAINS / : | Sottostringa del valore visualizzato, senza distinzione maiuscole/minuscole. |
> / >= / < / <= | Confronto numerico finito, accetta anche stringhe numeriche; esclude valori null/mancanti/vuoti/non numerici. |
IN | Il valore del campo appartiene a un CIDR IPv4 o IPv6 letterale. |
~ / =~ / !~ | Corrispondenza regex / negazione; flag nelle espressioni i, m, s, u. |
exists(field) | Il campo proprio è presente, anche con valore null. |
.other.field | Riferimento a un altro campo a destra, non stringa letterale. |
Plain text cerca testo letterale senza distinzione maiuscole/minuscole in tutti i valori appiattiti, compresi i campi nascosti. Regex compila l’intero input con il flag i. Expression supporta anche ricerche globali di testo, regex o CIDR. Gli array rimangono valori: una ricerca CIDR globale non visita ricorsivamente i loro elementi.
jq subset accetta .[] | select(expression) e passa l’espressione interna allo stesso parser. Supporta select(...) annidati, ma non trasformazioni jq, pipeline arbitrarie, iterazione sugli array o l’intera semantica di verità di jq.
flows.in.source_ip IN 10.0.0.0/8 AND (application ~ /dns/i OR duration >= 3600)
flows.in.source_ip != .flows.out.destination_ip
exists(source_nat_pool) AND source_nat_pool != null
.[] | select(.duration >= 3600 and .status == "active")
fieldName() genera percorsi semplici quando possibile e backtick con escape per nomi speciali/riservati. expression() valida e converte ogni modalità in un’espressione comune. convert() rifiuta conversioni che perderebbero significato. Il confronto CIDR usa aritmetica BigInt; IPv4 e IPv6 restano famiglie distinte, inclusi gli indirizzi IPv6 con IPv4 incorporato.
terms() estrae i termini AND di primo livello mantenendo intatti i gruppi OR/NOT. mergeConditions() elimina termini con testo identico, non tutte le espressioni logicamente equivalenti. joinTerms() aggiunge parentesi quando combina più termini.
08 · Search transactions, saved filters and Undo
Typing updates the draft through updateDraftState(). Header inputs call setColumn() to insert/update a visible CONTAINS term; Enter applies it. headerProjection() projects only independent, literal CONTAINS conditions. Multiple such conditions on one field disable that header input; conditions inside OR groups are not misleadingly projected as independent filters.
changeFilter(query, mode, options) is the common validated path for programmatic changes. It snapshots previous draft/applied state, updates the bar, and normally calls apply(false,false). run:false is used for mode changes and the example. Manual Apply records the previous applied state once, not every keystroke. setDraft() is a lower-level setter and deliberately does not validate or create history.
Saved filters support Add (merge independent AND terms) and Replace all. Validation happens before mutation. Clear, chip removal and right-click include/exclude also use the controller. A saved definition is copied into the expression; editing that definition later does not alter an existing search.
Undo stores at most 50 {draft, applied} snapshots in memory. It is disabled while busy, replays the old applied query and then restores its draft text. Revert edits only resets the draft to S.applied. Sorting, paging and layouts do not create Undo entries; loading/unloading clears history. Syntax errors leave successful results intact; a worker timeout unloads the dataset and clears history.
08 · Transazioni di ricerca, filtri salvati e Undo
La digitazione aggiorna la bozza tramite updateDraftState(). Gli input delle colonne chiamano setColumn() per inserire/aggiornare un termine CONTAINS visibile; Invio lo applica. headerProjection() proietta solo condizioni CONTAINS indipendenti con valori letterali. Più condizioni di questo tipo sullo stesso campo disabilitano l’input; quelle nei gruppi OR non vengono presentate erroneamente come filtri indipendenti.
changeFilter(query, mode, options) è il percorso comune validato per le modifiche programmatiche. Memorizza bozza e ricerca applicata precedenti, aggiorna la barra e normalmente chiama apply(false,false). run:false viene usato per cambi di modalità ed esempio. Apply manuale memorizza lo stato applicato precedente una sola volta, non a ogni carattere. setDraft() è una funzione di livello inferiore che intenzionalmente non valida e non crea cronologia.
I filtri salvati supportano Add (unione dei termini AND indipendenti) e Replace all. La validazione precede le modifiche. Anche Clear, rimozione dei chip e inclusione/esclusione dal menu contestuale usano il controller. La definizione salvata viene copiata nell’espressione; modificarla in seguito non cambia la ricerca già presente.
Undo conserva in memoria al massimo 50 snapshot {draft, applied}. È disabilitato durante le operazioni, esegue la vecchia ricerca applicata e poi ripristina la relativa bozza. Revert edits riporta solo la bozza a S.applied. Ordinamento, paginazione e layout non creano voci Undo; caricamento/scaricamento azzerano la cronologia. Gli errori di sintassi mantengono i risultati precedenti; un timeout del worker scarica il dataset e azzera la cronologia.
09 · Autocomplete and keyboard interaction
createCompletions(E) uses a tolerant lexer, separate from the strict execution parser. It determines whether the caret expects a field, operator, value, closing parenthesis or AND/OR link. Suggestions include all discovered fields, even hidden ones. It returns {items, start, end}; each item contains kind, label, detail and insertion text.
search-editor.js manages an accessible combobox/listbox with ▦ field and ƒ operator icons. It replaces only the token around the caret using setRangeText(), emits an input event and leaves execution to Apply/Enter. It supports arrows, Tab, Enter on a selected suggestion, Escape and Ctrl+Space; composition events suppress suggestions during IME input.
Do not make this lexer the authority for valid searches. A suggestion can edit an incomplete draft, whereas E.compile() remains the execution gate. New operators need both parser semantics and completion descriptions/examples.
09 · Autocompletamento e tastiera
createCompletions(E) usa un lexer tollerante, separato dal parser rigoroso di esecuzione. Determina se il cursore attende un campo, un operatore, un valore, una parentesi di chiusura o un collegamento AND/OR. I suggerimenti includono tutti i campi rilevati, anche quelli nascosti. Restituisce {items, start, end}; ogni voce contiene tipo, etichetta, dettagli e testo da inserire.
search-editor.js gestisce combobox/listbox accessibili con icone ▦ per i campi e ƒ per gli operatori. Sostituisce solo il token attorno al cursore tramite setRangeText(), emette un evento input e lascia l’esecuzione ad Apply/Invio. Supporta frecce, Tab, Invio su un suggerimento selezionato, Escape e Ctrl+Spazio; gli eventi di composizione sospendono i suggerimenti durante l’input IME.
Questo lexer non deve stabilire la validità delle ricerche. Un suggerimento può modificare una bozza incompleta, mentre E.compile() rimane il controllo prima dell’esecuzione. I nuovi operatori richiedono sia semantica nel parser sia descrizioni/esempi per l’autocompletamento.
10 · Rendering, pagination and virtual scrolling
render() rebuilds headers and delegates rows to renderRows(). A single headerProjection() result is reused for every header and syncHeaders(). Regular pages request 50, 100 (default) or 250 records. ALL activates virtualization rather than requesting the entire result set.
requestViewport() derives the visible indexes from logical scroll position, starts up to 40 records earlier and requests a window sized between a target minimum of 120 and a hard maximum of 250 (the returned final window may be shorter). A small boundary margin avoids fetching on every pixel. requestAnimationFrame coalesces scroll work; V.pending prevents overlapping window requests.
createRowHeights() stores height deltas in a Fenwick tree over a fixed estimate. Prefix sums and updates are O(log n); indexAt() binary-searches prefix sums, so it is O(log² n). Tables use fixed 40px rows. Nested records start with a 52px estimate; ResizeObserver measures expansions and adjusts scroll anchoring.
createScrollMap() compresses only offscreen spacers to a nominal 8,000,000px height budget. It maps physical scrollbar positions to logical dataset positions and back, avoiding browser height limits. The height index still consumes O(n) memory.
ALL uses fixed table layout plus minimum width 100%. Per-field base widths are sampled from up to 120 current records and retained for that dataset (180–420px base range; available space can expand them). Hidden/newly shown fields are measured when available. Horizontal scrolling remains available when the columns exceed the viewport.
Nested records request originals only when expanded. openTrees remembers paths by original index; mounted nodes are reused where possible. tree() lazily builds children in batches of 100. The record dialog uses the same tree builder. Neither mode renders all expanded data eagerly.
10 · Rendering, paginazione e scorrimento virtuale
render() ricostruisce gli header e delega le righe a renderRows(). Un solo risultato di headerProjection() viene riusato per tutti gli header e per syncHeaders(). Le pagine normali richiedono 50, 100 (predefinito) o 250 record. ALL attiva la virtualizzazione invece di richiedere tutti i risultati.
requestViewport() ricava gli indici visibili dalla posizione logica di scorrimento, parte fino a 40 record prima e richiede una finestra con minimo obiettivo 120 e massimo rigido 250 (l’ultima finestra restituita può essere più corta). Un piccolo margine evita richieste a ogni pixel. requestAnimationFrame raggruppa gli aggiornamenti; V.pending impedisce richieste di finestra sovrapposte.
createRowHeights() conserva le differenze di altezza in un albero di Fenwick rispetto a una stima fissa. Somme cumulative e aggiornamenti sono O(log n); indexAt() cerca con ricerca binaria sulle somme cumulative, quindi è O(log² n). Le righe tabellari sono alte 40px. I record annidati partono da una stima di 52px; ResizeObserver misura le espansioni e corregge l’ancoraggio dello scroll.
createScrollMap() comprime solo gli spaziatori fuori schermo entro un budget nominale di 8.000.000px. Converte le posizioni fisiche della scrollbar in posizioni logiche nel dataset e viceversa, evitando i limiti di altezza del browser. L’indice delle altezze richiede comunque memoria O(n).
ALL usa un layout tabellare fisso con larghezza minima 100%. Le larghezze base dei campi sono stimate su un massimo di 120 record correnti e mantenute per quel dataset (intervallo base 180–420px; lo spazio disponibile può ampliarle). I campi nascosti appena mostrati vengono misurati quando disponibili. Lo scorrimento orizzontale resta possibile se le colonne superano la larghezza della vista.
I record annidati richiedono gli originali solo all’espansione. openTrees ricorda i percorsi per indice originale; i nodi montati vengono riusati quando possibile. tree() costruisce i figli su richiesta in gruppi di 100. La finestra del record usa lo stesso costruttore. Nessuna delle due viste renderizza subito tutti i dati espansi.
11 · Columns and named layouts
S.columns includes hidden fields and defines their order. S.visible controls selection. Headers, cells, the Columns list and CSV export derive from these two structures. Drag/drop and Alt+Left/Right call moveColumn(); ×, checkboxes, Show all, Defaults and layout selection converge on columnsChanged().
columnsChanged(false) keeps checkbox DOM intact while updating layout status and the table; other mutations rebuild the list. This prevents the saved layout’s “modified” marker from lagging behind checkbox changes. The gear button and Columns button open the same selector.
{
"name": "NAT investigation",
"order": ["vdom", "flows.in.source_ip", "status"],
"visible": ["vdom", "flows.in.source_ip"]
}
layout-model.js validates unique names (1–100 characters), up to 100 layouts, and at most 10,000 entries in each order/visible list. Entries must be unique nonempty strings and visible must be a subset of order. matches() compares the live order/visibility to a resolution against the dataset baseline.
layout-manager.js handles selection, creation, update/rename, deletion, import/export and storage. Selecting Custom stops automatic reuse without resetting current columns. Imports merge by name but do not automatically apply the imported definition. Updating a layout on a different schema captures that current schema; absent fields are retained only until an explicit update.
11 · Colonne e layout con nome
S.columns include i campi nascosti e ne definisce l’ordine. S.visible controlla la selezione. Header, celle, elenco Columns e CSV derivano da queste due strutture. Drag/drop e Alt+Sinistra/Destra chiamano moveColumn(); ×, checkbox, Show all, Defaults e scelta del layout convergono su columnsChanged().
columnsChanged(false) mantiene intatto il DOM delle checkbox mentre aggiorna stato del layout e tabella; le altre modifiche ricostruiscono l’elenco. Questo evita che l’indicazione “modified” rimanga indietro rispetto alle checkbox. Il pulsante a ingranaggio e Columns aprono lo stesso selettore.
{
"name": "NAT investigation",
"order": ["vdom", "flows.in.source_ip", "status"],
"visible": ["vdom", "flows.in.source_ip"]
}
layout-model.js valida nomi univoci (1–100 caratteri), fino a 100 layout e al massimo 10.000 elementi in ciascuna lista order/visible. Gli elementi devono essere stringhe univoche non vuote e visible deve essere un sottoinsieme di order. matches() confronta ordine/visibilità correnti con una risoluzione rispetto ai campi iniziali del dataset.
layout-manager.js gestisce selezione, creazione, aggiornamento/rinomina, cancellazione, import/export e persistenza. Custom interrompe il riuso automatico senza azzerare le colonne correnti. Gli import uniscono per nome ma non applicano automaticamente la definizione importata. Aggiornare un layout su uno schema diverso acquisisce quello schema: i campi assenti vengono conservati solo fino a un aggiornamento esplicito.
12 · Sorting, export and local persistence
compareValues() defines a transitive type order: missing → null → boolean → number → string → array/object. Numbers sort numerically; strings use Intl.Collator(undefined, {numeric:true}). Numeric strings remain strings. Descending reverses the comparison; original row index breaks ties. Natural string ordering can depend on browser locale.
JSON export includes all matching original records in current sort order, as an array. CSV includes all matching rows and only visible fields in current order; cells are quoted and embedded quotes doubled. Potential spreadsheet formulas, including ones after leading whitespace, receive a protective apostrophe. Right-clicking a header exports distinct displayed values as TXT, not a typed JSON set. Copy/include/exclude actions use a cell’s underlying value; include/exclude are offered only for present scalar/null values.
Exports are built as Blobs in the worker and returned to the UI. download() creates an object URL and revokes it after 30 seconds. Export uses the worker’s accepted matches at processing time; subsequent query messages do not change a completed export snapshot. Draft edits alone never alter it. Export assembly still consumes memory proportional to output size.
| Storage key | Payload |
|---|---|
sessionview-v2-theme | light or dark |
sessionview-v2-presets | JSON array of {name, mode, query} |
json-inspect-column-layouts-v1 | {version:1, selected:"name", layouts:[...]} |
storage() catches denied/quota errors. Presets/layouts remain usable for the session and save actions report the need to export a backup. Filesystem origins have browser-specific storage behavior: moving the folder or changing browsers can change the storage area.
Bundled filters.js/layouts.js provide defaults. Valid browser-saved libraries take precedence. Imports accept strict JSON arrays or the matching window.FILTER_PRESETS = [...]/window.COLUMN_LAYOUTS = [...] form; parsePresetFile() parses data and rejects trailing executable code. Bundled scripts themselves execute as normal scripts and must be trusted. The app cannot silently rewrite them: export and manually replace the file to distribute defaults.
12 · Ordinamento, export e persistenza locale
compareValues() definisce un ordine transitivo dei tipi: mancante → null → booleano → numero → stringa → array/oggetto. I numeri sono ordinati numericamente; le stringhe usano Intl.Collator(undefined, {numeric:true}). Le stringhe numeriche rimangono stringhe. La direzione discendente inverte il confronto; a parità viene usato l’indice originale. L’ordinamento naturale delle stringhe può dipendere dalla lingua del browser.
L’export JSON include tutti i record originali corrispondenti nell’ordinamento corrente, come array. Il CSV include tutti i record corrispondenti e solo i campi visibili nell’ordine corrente; le celle sono racchiuse tra virgolette e le virgolette interne raddoppiate. Le possibili formule per fogli di calcolo, anche precedute da spazi, ricevono un apostrofo protettivo. Il clic destro su un header esporta valori visualizzati distinti in TXT, non un insieme JSON con tipi. Copia/includi/escludi usano il valore della cella; includi/escludi sono disponibili solo per valori scalari/null presenti.
Gli export sono costruiti come Blob nel worker e restituiti all’interfaccia. download() crea un URL oggetto e lo revoca dopo 30 secondi. L’export usa le corrispondenze accettate dal worker al momento dell’elaborazione; i messaggi di ricerca successivi non modificano uno snapshot già esportato. Le sole modifiche alla bozza non lo alterano. La costruzione dell’export richiede comunque memoria proporzionale all’output.
| Chiave di storage | Contenuto |
|---|---|
sessionview-v2-theme | light oppure dark |
sessionview-v2-presets | Array JSON di {name, mode, query} |
json-inspect-column-layouts-v1 | {version:1, selected:"name", layouts:[...]} |
storage() intercetta accesso negato e quote esaurite. Filtri/layout rimangono utilizzabili nella sessione e i salvataggi segnalano la necessità di esportare una copia. Le origini filesystem hanno comportamenti di storage specifici del browser: spostare la cartella o cambiare browser può cambiare l’area di memorizzazione.
I file inclusi filters.js/layouts.js forniscono i valori predefiniti. Le librerie valide salvate nel browser hanno precedenza. Gli import accettano array JSON rigorosi o la forma corrispondente window.FILTER_PRESETS = [...]/window.COLUMN_LAYOUTS = [...]; parsePresetFile() interpreta dati e rifiuta codice eseguibile aggiuntivo. I file inclusi sono invece script normali e devono essere attendibili. L’app non può riscriverli silenziosamente: esportare e sostituire manualmente il file per distribuire i valori predefiniti.
13 · Failure modes and performance boundaries
| Boundary | Current implementation |
|---|---|
| JSON depth | 256 object/array levels |
| Expression | 16,384 characters; 1,024 tokens; 64 parenthesis levels / consecutive NOTs |
| Worker page | 0–250 records; nonnegative integer offset |
| Metadata import | File size ≤ 1 MiB |
| Timeout | 30s default; 90s load, source selection and export |
| Undo | 50 snapshots, in memory |
| Nested children | 100 per expansion batch |
watch() checks the operation ID before timing out. Timeout calls unload(), terminates the worker and asks the user to reopen the file; it does not preserve the active dataset. Native regular expressions can have expensive backtracking: syntax validation does not guarantee cheap execution. UI responsiveness comes from worker isolation and the timeout, not a linear-time regex engine.
JSON.parse does not preserve duplicate keys, formatting or unsafe integers exactly. validateDocument() reports unsafe integers and rejects non-finite numeric overflow and excessive depth. Identifiers requiring exact large values should arrive as strings. Data and field names are inserted with textContent; imported filter/layout code is not evaluated.
Loading retains the parsed document, a flattened projection and index lists. JSONL currently reads the whole text and splits lines; it is not streaming. The validator’s traversal stack and export strings/Blobs also contribute to peak memory. Filtering is a full scan; sorting is O(m log m) over m matches. Rendering is bounded by the window, but the number of fields and the size of individual array/object values still matter.
The app’s scripts make no network requests. Do not add external fonts, CDN libraries, runtime fetches or module loading assumptions without testing local-file operation. Browser/enterprise policy can restrict workers, clipboard or storage; clipboard has a selection/copy fallback. The automated browser coverage is Chromium, not a guarantee for every desktop browser.
13 · Errori e limiti di prestazioni
| Limite | Implementazione corrente |
|---|---|
| JSON depth | 256 livelli di oggetti/array |
| Expression | 16.384 caratteri; 1.024 token; 64 livelli di parentesi / NOT consecutivi |
| Worker page | 0–250 record; offset intero non negativo |
| Metadata import | Dimensione file ≤ 1 MiB |
| Timeout | 30s predefiniti; 90s per caricamento, selezione raccolta ed export |
| Undo | 50 snapshot, in memoria |
| Nested children | 100 per gruppo di espansione |
watch() controlla l’ID dell’operazione prima del timeout. Il timeout chiama unload(), termina il worker e chiede di riaprire il file; non conserva il dataset attivo. Le regex native possono avere backtracking costoso: la validazione sintattica non garantisce un’esecuzione economica. La reattività dell’interfaccia deriva dall’isolamento del worker e dal timeout, non da un motore regex a tempo lineare.
JSON.parse non preserva chiavi duplicate, formattazione o interi oltre la precisione sicura. validateDocument() segnala gli interi non sicuri e rifiuta overflow numerico non finito e profondità eccessiva. Gli identificatori grandi che richiedono precisione esatta devono arrivare come stringhe. Dati e nomi dei campi vengono inseriti con textContent; il codice dei filtri/layout importati non viene eseguito.
Il caricamento mantiene documento interpretato, proiezione appiattita e liste di indici. Il JSONL viene attualmente letto interamente e diviso in righe: non è streaming. Anche stack di validazione e stringhe/Blob di export contribuiscono al picco di memoria. Il filtro è una scansione completa; l’ordinamento è O(m log m) su m corrispondenze. Il rendering è limitato alla finestra, ma contano anche numero di campi e dimensione dei singoli valori array/oggetto.
Gli script dell’app non effettuano richieste di rete. Non aggiungere font esterni, librerie CDN, fetch durante l’esecuzione o assunzioni sul caricamento dei moduli senza verificare l’apertura da filesystem. Le policy del browser/aziendali possono limitare worker, appunti o storage; la copia ha un’alternativa basata su selezione/copia. La copertura automatica browser riguarda Chromium, non garantisce tutti i browser desktop.
14 · Tests, debugging and release checks
Run commands from sessionview-v2/. Node is only a development dependency. The four non-browser suites cover the core engine, completion model, review regressions and layout model.
node tests/run-tests.cjs
# Playwright must be installed separately for development.
# Replace both paths with your local installation paths.
PLAYWRIGHT_MODULE=/absolute/path/to/node_modules/playwright \
CHROMIUM_PATH=/absolute/path/to/chrome \
node tests/run-tests.cjs --browser
The nine application browser suites open file:// with networking disabled and cover imports, filtering, editing, columns, completion, virtual scrolling, delayed replies, layouts and review fixes/Undo. Fixtures include ../tips-is.json and ../vr5-flows.json relative to the application directory. Keep them available when running tests. A separate documentation smoke test checks this file independently, bringing the full runner to 14 suites.
Useful checkpoints: inspect S.applied versus the input and S.editMode; compare S.submitted and current IDs for delayed queries; inspect V.start/end/pending for missing windows; inspect S.columns/S.visible and the layout baseline for column issues. These globals are available from DevTools; avoid dumping sensitive records into shared logs.
Before shipping changes, test a cold local-file launch, both supplied samples, ALL at the beginning/middle/end, nested expansion, sort/filter after scrolling, saved-layout schema differences, export with a pending draft, unavailable storage and invalid imports. Add a regression reproducing a bug before broadening the implementation. Application files must stay together; this guide alone is self-contained.
14 · Test, debugging e verifica del rilascio
Eseguire i comandi da sessionview-v2/. Node è una dipendenza solo di sviluppo. Le quattro suite senza browser coprono motore, modello di autocompletamento, regressioni della review e modello dei layout.
node tests/run-tests.cjs
# Playwright va installato separatamente per lo sviluppo.
# Sostituire entrambi i percorsi con quelli locali.
PLAYWRIGHT_MODULE=/percorso/assoluto/node_modules/playwright \
CHROMIUM_PATH=/percorso/assoluto/chrome \
node tests/run-tests.cjs --browser
Le nove suite browser dell’app aprono file:// con rete disabilitata e coprono import, filtri, editor, colonne, suggerimenti, scorrimento virtuale, risposte ritardate, layout e correzioni/Undo. Le fixture includono ../tips-is.json e ../vr5-flows.json rispetto alla cartella applicativa. Mantenerle disponibili durante i test. Uno smoke test separato verifica questa documentazione, portando il runner completo a 14 suite.
Controlli utili: confrontare S.applied con input e S.editMode; verificare S.submitted e ID correnti per ricerche ritardate; esaminare V.start/end/pending per finestre mancanti; verificare S.columns/S.visible e i campi iniziali del layout per problemi di colonne. Questi globali sono accessibili da DevTools; evitare di riversare record sensibili in log condivisi.
Prima di distribuire modifiche, verificare l’avvio locale a freddo, entrambi i file di esempio, ALL a inizio/metà/fine, espansione annidata, ordinamento/filtri dopo lo scroll, layout su schemi diversi, export con bozza non applicata, storage non disponibile e import non validi. Aggiungere una regressione che riproduca il bug prima di ampliare l’implementazione. I file applicativi devono rimanere insieme; questa guida da sola è autonoma.
15 · Extension recipes and design priorities
| Change | Implementation route |
|---|---|
| Add an operator | Extend core.js tokenizer/compile, then completions.js descriptions and examples. Test types, null/missing, precedence, invalid syntax and conversion. Preserve worker self-containment. |
| Add a filter action | Build an expression, then call changeFilter(). Use run:false only for draft-only actions. Do not mutate S.applied or keep a second hidden predicate. |
| Add a column action | Update S.columns/S.visible and call columnsChanged(). Check keyboard focus, layout modified state, CSV order and virtual scroll anchoring. |
| Add a worker operation | Choose request ID ownership, response/error shape and op semantics. Gate stale replies; test late delivery and worker replacement. |
| Change row geometry | Coordinate styles.css, the 40/52px estimates, spacer mapping and ResizeObserver. Check start/middle/end jumps and expanded nested rows. |
| Evolve saved metadata | Version the schema, retain old keys/migrate deliberately, validate imports before mutation and keep an export path for recovery. |
Keep tests focused on observable behavior and invariants. The important cross-feature contracts are: draft versus applied search, complete expression visibility, stable row identity, independent column/layout state, bounded rendering, validated metadata and correlated asynchronous replies.
Future streaming JSONL import, indexed search, full jq or vendor adapters are separate design projects, not small parser switches. Measure actual file sizes and workflows first. Adding a framework is not required for the current local-file architecture.
15 · Ricette di estensione e priorità progettuali
| Modifica | Percorso di implementazione |
|---|---|
| Aggiungere un operatore | Estendere tokenizer/compile in core.js, poi descrizioni ed esempi in completions.js. Testare tipi, null/mancanti, precedenza, sintassi errata e conversioni. Mantenere il motore autonomo per il worker. |
| Aggiungere un’azione filtro | Costruire un’espressione e chiamare changeFilter(). Usare run:false solo per azioni sulla bozza. Non modificare S.applied e non conservare un secondo predicato nascosto. |
| Aggiungere un’azione sulle colonne | Aggiornare S.columns/S.visible e chiamare columnsChanged(). Verificare focus tastiera, stato modified del layout, ordine CSV e ancoraggio dello scroll virtuale. |
| Aggiungere un’operazione worker | Definire chi possiede gli ID, formato di risposta/errore e semantica di op. Scartare risposte obsolete; testare ritardi e sostituzione del worker. |
| Modificare la geometria delle righe | Coordinare styles.css, stime di 40/52px, mappa degli spaziatori e ResizeObserver. Verificare salti a inizio/metà/fine e righe annidate espanse. |
| Evolvere i metadati salvati | Versionare lo schema, mantenere le vecchie chiavi o migrare esplicitamente, validare prima delle modifiche e conservare un export per il recupero. |
Concentrare i test su comportamenti osservabili e invarianti. I contratti importanti tra funzionalità sono: bozza rispetto a ricerca applicata, espressione completa visibile, identità stabile dei record, indipendenza di colonne/layout, rendering limitato, metadati validati e risposte asincrone correlate.
Import JSONL in streaming, ricerca indicizzata, jq completo o adattatori dei produttori sono progetti separati, non piccole opzioni del parser. Misurare prima dimensioni reali dei file e flussi di lavoro. L’architettura locale corrente non richiede l’introduzione di un framework.