Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion npm_modules/cli/debugger/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ the CLI package.
- `debugger-actions.js`: UI actions, command prompt handling, auto-refresh, and externally driven debugger actions.
- `debugger-session.js`: `sessionStorage` restore/persist for reload-friendly debugger state.
- `debugger-bootstrap.js`: DOM event wiring and boot sequence.
- `devtools-panel.html`, `devtools-panel.css`, and `devtools-panel.js`: the focused Chromium Elements, Console, and bounded Performance panel embedded by the generated extension.
- `devtools-panel.html`, `devtools-panel.css`, and `devtools-panel.js`: the focused Chromium Elements, State, Console, and bounded Performance panel embedded by the generated extension.

Scripts are loaded as classic browser scripts in the order listed in
`index.html`. There is no module loader or bundler for this frontend; shared
Expand Down Expand Up @@ -61,6 +61,45 @@ revoked Proxy therefore causes that component's properties to be omitted while
preserving the hierarchy. Prototype traversal is not used, and property values
are never read through normal property access.

The same web hierarchy snapshot may include read-only component runtime state
without adding a route, capability, or renderer API. This is taken from the
exact component instance, not its ViewModel, and only from an own, enumerable
data descriptor named `state` whose value is a non-null object. Getters,
inherited fields, scalar state, and throwing reflection are omitted. The state
object is detached under the existing descriptor-only snapshot rules, encoded
as JSON, and admitted only when the exact escaped `component.state` wire string
fits the component snapshot's remaining 64 KiB UTF-8 budget after properties.
Detached and escaped-wire projection work is also bounded across the complete
hierarchy by separate byte, reflection, and attempt counters; indeterminate
nested reflection exhausts that State-only work budget without affecting
component properties or hierarchy capture. Own-key enumeration itself is not
resumable, so a Proxy may still materialize one complete key list. State checks
that list against its remaining reflection allowance before performing any
per-name descriptor lookup, and checks the allowance again before every nested
object or array descriptor lookup.
The pre-existing property-edit metadata is not debited from that counter and
remains bounded by the complete snapshot envelope. Topology revalidation binds
the same virtual node and component instance; an ineligible descriptor or
changed raw state identity removes only `component.state`, while ViewModel
replacement continues to remove only properties and edit metadata. If the
final hierarchy envelope is too large, runtime state is stripped and retried
before existing component properties and edit metadata are stripped.

Native detailed snapshots retain their existing human-readable
`IRenderedVirtualNodeData.state` string and are not changed by this web capture.
The State panel determines the serialization format from the target identity.
Inspected Chromium web snapshots accept strict JSON only. Native debug strings
do not escape keys, quoted contents, function names, error text, or marker text,
so every direct native snapshot is displayed as escaped raw text rather than
interpreted structurally. Raw display remains searchable. Input is capped at
65,536 characters; parsed web input is additionally capped at depth 12, tokens 2,000,
entries per container at 100, and keys at 1,024 characters. Parsed records have
null prototypes and are populated with data descriptors. Rendering is further
capped at 500 stateful components and 1,000 value rows. Main-panel search and
disclosures are independent from selected-component State details, and every
Inspect action is bound to the exact current snapshot node and render
generation.

An accepted web snapshot may additionally promote the separate
`component-property-edit` capability when both the renderer's dedicated
full-ViewModel mutation API and secure browser randomness are available. Static
Expand Down
183 changes: 183 additions & 0 deletions npm_modules/cli/debugger/devtools-panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,189 @@ button {
word-break: break-word;
}

.runtime-state-toolbar {
justify-content: flex-start;
gap: 9px;
}

.runtime-state-summary {
color: var(--muted);
font-size: 10px;
font-weight: 400;
white-space: nowrap;
}

.runtime-state-filter {
width: min(320px, 48vw);
min-width: 120px;
height: 23px;
margin-left: auto;
padding: 0 7px;
border: 1px solid var(--border);
border-radius: 3px;
outline: none;
background: var(--background);
color: var(--text);
font-size: 11px;
}

.runtime-state-filter:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent);
}

.runtime-state-content {
padding: 0;
font-family: var(--mono);
font-size: 11px;
}

.runtime-state-component {
position: relative;
border-bottom: 1px solid var(--border-soft);
}

.runtime-state-component-details {
min-width: 0;
}

.runtime-state-component-summary,
.runtime-state-value-summary {
display: flex;
min-width: 0;
align-items: center;
list-style: none;
cursor: pointer;
}

.runtime-state-component-summary {
min-height: 34px;
padding: 4px 68px 4px 9px;
gap: 7px;
}

.runtime-state-component-summary::-webkit-details-marker,
.runtime-state-value-summary::-webkit-details-marker {
display: none;
}

.runtime-state-component-summary:hover,
.runtime-state-value-summary:hover,
.runtime-state-inspect:hover,
.runtime-state-inspect:focus-visible {
background: var(--surface-hover);
}

.runtime-state-disclosure {
width: 10px;
flex: 0 0 auto;
color: var(--muted);
}

.runtime-state-component-details[open] > .runtime-state-component-summary > .runtime-state-disclosure,
details[open] > .runtime-state-value-summary > .runtime-state-disclosure {
transform: rotate(90deg);
}

.runtime-state-component-name {
min-width: 0;
overflow: hidden;
color: var(--tag);
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}

.runtime-state-component-key,
.runtime-state-component-description,
.runtime-state-limit {
color: var(--muted);
}

.runtime-state-component-key,
.runtime-state-component-description {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.runtime-state-component-description {
margin-left: auto;
}

.runtime-state-inspect {
position: absolute;
top: 5px;
right: 8px;
flex: 0 0 auto;
padding: 3px 6px;
border: 0;
border-radius: 3px;
background: transparent;
color: var(--accent);
}

.runtime-state-component-body {
padding: 4px 10px 9px 25px;
border-top: 1px solid var(--border-soft);
}

.runtime-state-value-row {
min-height: 20px;
overflow-wrap: anywhere;
}

.runtime-state-value-details {
margin-left: 10px;
}

.runtime-state-value-summary {
min-height: 20px;
gap: 4px;
}

.runtime-state-value-children {
margin-left: 10px;
padding-left: 8px;
border-left: 1px solid var(--border-soft);
}

.runtime-state-value-key {
color: var(--attribute);
}

.runtime-state-value-string {
color: var(--string);
}

.runtime-state-value-number,
.runtime-state-value-boolean,
.runtime-state-value-null {
color: var(--number);
}

.runtime-state-value-description {
color: var(--muted);
}

.runtime-state-raw {
max-height: 260px;
margin: 0;
overflow: auto;
white-space: pre-wrap;
word-break: break-word;
}

.runtime-state-inspector {
margin-bottom: 12px;
}

.runtime-state-limit {
padding: 7px 0;
font-style: italic;
}

.breadcrumb-bar {
padding: 0 8px;
border-top: 1px solid var(--border);
Expand Down
88 changes: 84 additions & 4 deletions npm_modules/cli/debugger/devtools-panel.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@
>
Elements
</button>
<button
id="stateTab"
type="button"
class="main-tab"
data-section="state"
role="tab"
aria-controls="stateSection"
aria-selected="false"
tabindex="-1"
>
State
</button>
<button
id="performanceTab"
type="button"
Expand Down Expand Up @@ -115,16 +127,61 @@
<section class="inspector-pane" aria-label="Selected element inspector">
<div class="inspector-toolbar">
<nav class="detail-tabs" aria-label="Element inspection" role="tablist">
<button class="detail-tab selected" data-detail="styles" role="tab" aria-selected="true">Styles</button>
<button class="detail-tab" data-detail="computed" role="tab" aria-selected="false">Computed</button>
<button class="detail-tab" data-detail="dom" role="tab" aria-selected="false">DOM</button>
<button
id="stylesDetailTab"
type="button"
class="detail-tab selected"
data-detail="styles"
role="tab"
aria-controls="inspector"
aria-selected="true"
tabindex="0"
>
Styles
</button>
<button
id="computedDetailTab"
type="button"
class="detail-tab"
data-detail="computed"
role="tab"
aria-controls="inspector"
aria-selected="false"
tabindex="-1"
>
Computed
</button>
<button
id="stateDetailTab"
type="button"
class="detail-tab"
data-detail="state"
role="tab"
aria-controls="inspector"
aria-selected="false"
tabindex="-1"
>
State
</button>
<button
id="domDetailTab"
type="button"
class="detail-tab"
data-detail="dom"
role="tab"
aria-controls="inspector"
aria-selected="false"
tabindex="-1"
>
DOM
</button>
</nav>
<div class="toolbar-spacer"></div>
<button id="copyNodeButton" class="icon-button" title="Copy selected node JSON" aria-label="Copy node">
<svg viewBox="0 0 20 20" aria-hidden="true"><path d="M7 6h9v10H7zM4 13V4h9" /></svg>
</button>
</div>
<div id="inspector" class="inspector"></div>
<div id="inspector" class="inspector" role="tabpanel" aria-labelledby="stylesDetailTab"></div>
</section>

<footer class="breadcrumb-bar">
Expand All @@ -133,6 +190,29 @@
</footer>
</section>

<section
id="stateSection"
class="section utility-section"
data-panel="state"
role="tabpanel"
aria-labelledby="stateTab"
hidden
>
<div class="section-header runtime-state-toolbar">
<span>Component state</span>
<span id="stateSummary" class="runtime-state-summary" role="status" aria-live="polite"></span>
<input
id="stateFilter"
class="runtime-state-filter"
type="search"
placeholder="Filter components, keys, or values"
aria-label="Filter component state"
autocomplete="off"
/>
</div>
<div id="stateContent" class="utility-content runtime-state-content"></div>
</section>

<section
id="performanceSection"
class="section utility-section"
Expand Down
Loading
Loading