Skip to content

feat: web explorer web three way nav - #1706

Merged
megha-narayanan merged 1 commit into
aws:feat/cdk-explorerfrom
megha-narayanan:feat/explorer-web-three-way-nav-clean
Jul 9, 2026
Merged

feat: web explorer web three way nav#1706
megha-narayanan merged 1 commit into
aws:feat/cdk-explorerfrom
megha-narayanan:feat/explorer-web-three-way-nav-clean

Conversation

@megha-narayanan

@megha-narayanan megha-narayanan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

A full cdk explore UI over the synthesized assembly. Builds on #1624.

Features

  • Three panes: construct tree, source, template, with syntax highlighting for all CDK source languages (TypeScript, JavaScript, Python, Java, C#, Go)
  • Template pane: JSON/YAML toggle
  • Linked navigation by double-click: tree → source + template, template → source, and source → template
  • Violations panel grouped by rule (click an occurrence to jump to its construct), inline diagnostic squiggles in the source pane, and severity coloring in the tree (inherited up to ancestors)
  • Resizable split panes and an "Open" file picker for either pane

Design decisions

  • Custom line renderer over PrismJS tokens, not Prism's HTML. syntax.ts calls Prism.tokenize and flattens the token tree into per-line token arrays (Prism tokens can straddle newlines). CodeViewer then renders each line itself, which is what lets one component compose four things per line: syntax colors, the nav-highlight band, scroll-to-line, and column-accurate diagnostic squiggles. Prism's string output can't be sliced per line or overlaid with diagnostics.
  • PrismJS grammars, not a full editor/highlighter. This bundles into the shipped CLI via esbuild, so Monaco-scale dependencies are probably too big. Prism core plus JSON, YAML, and the CDK source languages keeps it small, and one CodeViewer serves both panes.
  • Navigate by logical ID, not line numbers. JSON and YAML render at different lines, so navigation carries only the logical ID and the template viewer resolves the highlight line in whichever format is on screen.
  • Source → template is nearest-preceding, ties to the top-most construct. Synthesized children share their parent's single creation line (every subnet/NAT under a new ec2.Vpc(...)), so a click there resolves to the authored parent, not a child.
Screenshot 2026-07-06 at 12 08 28 PM

Checklist

  • This change contains a major version upgrade for a dependency and I confirm all breaking changes are addressed
    • Release notes for the new version:

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

@github-actions github-actions Bot added the p2 label Jul 6, 2026
@aws-cdk-automation
aws-cdk-automation requested a review from a team July 6, 2026 18:28
@megha-narayanan
megha-narayanan marked this pull request as ready for review July 6, 2026 18:49
@megha-narayanan
megha-narayanan force-pushed the feat/explorer-web-three-way-nav-clean branch from 03968c6 to a27c8e5 Compare July 6, 2026 19:57
@rix0rrr

rix0rrr commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Love it! Initial response to the PR body:

Linked navigation by double-click: tree → source + template, template → source, and source → template

Why double-click? I think single clicks would seem more logical?


EDIT: Oh I think I see why you're doing double-click: to make a distinction between expand/collapse and "navigate to".

Alternative suggestion: make the expand/collapse arrow icon have a larger clickable area, and make that its own click target. For people that want to only expand/collapse they can click the arrow icon, or they can click the full label to "expand and navigate to". Clicking the label should not collapse.

@rix0rrr rix0rrr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great!

Quick question, are the panes resizable?

Comment on lines +1 to +9
declare module 'prismjs/components/prism-core' {
import Prism from 'prismjs';
export default Prism;
}
declare module 'prismjs/components/prism-json' {}
declare module 'prismjs/components/prism-yaml' {}
declare module 'prismjs/components/prism-clike' {}
declare module 'prismjs/components/prism-javascript' {}
declare module 'prismjs/components/prism-typescript' {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this? Does Prism not come iwth its own type definitions?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@types/prismjs only ships declarations for the top-level prismjs entry and the prismjs/components metadata module, not the individual prismjs/components/prism-* files. here im importing the deep paths on purpose, because prism-core is the minimal core, so we bundle only the languages we explicitly register instead of Prism's default auto-loaded set, plus one side-effect import per CDK language.


async function getCachedAssembly(): Promise<AssemblyReadResult> {
const now = Date.now();
if (cachedAssembly && (now - cachedAssembly.timestamp) < CACHE_TTL_MS) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the best invalidation we can do?

Why not look at the timestamp of manifest.json or something like that?

Also -- what about torn reads? I read some things from an old assembly, then it updates, and then I read some more things from a new assembly? And what happens to the read lock?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok so i took a look at this and basically entirely re did it. switched it to invalidate on manifest.json's mtime like you suggested.

each request stats manifest.json. if the mtime hasn't changed since the last read it serves the cached assembly and doesn't touch the lock at all. if it changed (or it's the first read) it re-reads under the read lock and caches against the new mtime. bonus: if a request comes in mid-synth before the manifest gets rewritten, the unchanged mtime just keeps serving the last complete generation instead of fighting for the lock.

on torn reads: the re-read holds the read lock so a synth can't rewrite cdk.out under it, and each cached entry is one whole generation. within a request the tree, violations and template index all come from that one snapshot so they can't be a mix of old and new. one thing to be clear about, the mtime isn't the guarantee (a synth writes a bunch of files and the manifest isn't necessarily written last), the lock is what guarantees a finished read, the mtime is just the cache key. the case still open is skew across separate requests (load /tree, a synth happens, then load /template).

on the read lock: there just wasn't one on the web path before, that was the actual gap. now it grabs the same lock the LSP uses (fromAssemblyDirectory().produce()). if the write lock is held it retries a bit then returns 503 instead of serving a half-written read, and a non-lock failure returns 500 instead of hanging.

heads up, sharing that lock meant moving the acquire helper out of lib/lsp into lib/core so i touched a couple LSP files too. kept those in their own commit and i'll open a matching LSP PR once this merges so the two branches base off each other cleanly.

@megha-narayanan

Copy link
Copy Markdown
Contributor Author

This is looking great!

Quick question, are the panes resizable?

Yep, the construct tree and violations panels are both resizable and collapsable (see the little arrow pills here for collapsing) and then you can drag anywhere on the border to resize. the code panels are not independently resizable.

Screenshot 2026-07-06 at 12 08 53 PM

@megha-narayanan
megha-narayanan requested a review from rix0rrr July 8, 2026 13:15
megha-narayanan added a commit that referenced this pull request Jul 8, 2026
Matching LSP side of #1706. Moves the assembly read-lock acquirer out of
lib/lsp into lib/core/assembly-lock.ts so the LSP and web server build
the
read lock from one shared factory. Pure refactor, LSP behavior is
unchanged,
and server.ts re-exports AssemblyLock so existing importers keep
resolving it.

Fixes #

### Checklist
- [ ] This change contains a major version upgrade for a dependency and
I confirm all breaking changes are addressed
  - Release notes for the new version:

---
By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license
…and template

Rebased aws#1706 onto the updated feat/cdk-explorer (now carrying feat/cdk-lsp)
as a single integration commit. Adds source/template/tree navigation with
syntax highlighting, a YAML template view, and a file picker, and serves each
assembly read under the Toolkit read lock via the factory core extracted in
aws#1715. Coexists with the SSE live-refresh from aws#1698: the reload effect and
the navigation state share the same App shell.
@megha-narayanan
megha-narayanan merged commit bd5fa61 into aws:feat/cdk-explorer Jul 9, 2026
9 of 40 checks passed
@megha-narayanan
megha-narayanan deleted the feat/explorer-web-three-way-nav-clean branch July 9, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants