Skip to content

feat(workspace): panel-preset switcher over Dockview - #120

Open
jerelvelarde wants to merge 3 commits into
a2ui-project:mainfrom
jerelvelarde:jerel/pr2-panel-switcher
Open

feat(workspace): panel-preset switcher over Dockview#120
jerelvelarde wants to merge 3 commits into
a2ui-project:mainfrom
jerelvelarde:jerel/pr2-panel-switcher

Conversation

@jerelvelarde

@jerelvelarde jerelvelarde commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

A segmented switcher fronting the Dockview workspace with named one-click layout presets, on top of the existing freeform Dockview.

What

  • Three presets — Chat, Chat + Preview (new render-focused default), Code & Engine (full JSON editor + data/engine panels).
  • applyPreset() clears and rebuilds Dockview per preset; freeform drag-and-save (composer_dockview_layout) is preserved untouched.
  • New composer_workspace_preset key drives the toggle highlight.
  • Files: composer-workspace.{ts,ng.html,scss,spec.ts}.

Why

Fast, named layout arrangements while keeping the freeform Dockview underneath.

Testing

Workspace unit spec passing; ng build clean.

Notes

Styles reference the theme PR's --cpk-* tokens and fall back to Material without it — functionally standalone.


Contributed by CopilotKit.

Screenshots

The layout toggle lives in the header (single cycle button through Chat / Chat + Preview / Code & Engine):

Light Dark

@google-cla

google-cla Bot commented Jul 22, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces workspace layout presets ('chat', 'chat-preview', and 'full') to the ComposerWorkspace component, allowing users to toggle between different panel configurations. It also updates the workspace styles to align with the CopilotKit token palette and adds comprehensive unit tests for the new presets. The feedback suggests wrapping localStorage operations in try-catch blocks to prevent runtime errors in restricted environments, refactoring the preset selection logic to use a safer switch statement, and cleaning up the now-obsolete extension-mode class and isExtension signal.

Comment on lines +328 to +331
const savedPreset = localStorage.getItem(WORKSPACE_PRESET_KEY);
if (savedPreset === 'chat' || savedPreset === 'chat-preview' || savedPreset === 'full') {
this.activePreset.set(savedPreset);
}

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.

medium

Accessing localStorage can throw a SecurityError or DOMException in environments where storage access is restricted (e.g., third-party iframe sandboxes or when cookies/local storage are disabled by the user). Wrapping the localStorage.getItem call in a try-catch block ensures the component initializes safely without crashing.

    try {
      const savedPreset = localStorage.getItem(WORKSPACE_PRESET_KEY);
      if (savedPreset === 'chat' || savedPreset === 'chat-preview' || savedPreset === 'full') {
        this.activePreset.set(savedPreset);
      }
    } catch (e) {
      console.warn('Failed to read workspace preset from localStorage', e);
    }

Comment on lines +340 to +352
applyPreset(preset: WorkspacePreset): void {
if (!this.isDockviewInitialized()) return;
this.dockviewApi.clear();
if (preset === 'chat') {
this.buildChatLayout();
} else if (preset === 'chat-preview') {
this.buildChatPreviewLayout();
} else {
this.buildFullLayout();
}
this.activePreset.set(preset);
localStorage.setItem(WORKSPACE_PRESET_KEY, preset);
}

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.

medium

Using an explicit switch statement with a default fallback is safer and more maintainable than an if-else chain that defaults to the 'full' layout. If an invalid or unexpected preset value is passed at runtime, it should fall back to the default 'chat-preview' preset. Additionally, wrapping localStorage.setItem in a try-catch block prevents runtime crashes in environments where storage access is restricted.

  applyPreset(preset: WorkspacePreset): void {
    if (!this.isDockviewInitialized()) return;
    this.dockviewApi.clear();
    switch (preset) {
      case 'chat':
        this.buildChatLayout();
        break;
      case 'chat-preview':
        this.buildChatPreviewLayout();
        break;
      case 'full':
        this.buildFullLayout();
        break;
      default:
        this.buildChatPreviewLayout();
        preset = 'chat-preview';
        break;
    }
    this.activePreset.set(preset);
    try {
      localStorage.setItem(WORKSPACE_PRESET_KEY, preset);
    } catch (e) {
      console.warn('Failed to save workspace preset to localStorage', e);
    }
  }

Comment on lines +27 to 29
}

.workspace-toolbar {
flex: 0 0 auto;
display: flex;
align-items: center;
gap: var(--cpk-space-3);
padding: var(--cpk-space-2) var(--cpk-space-3);
}

&.extension-mode {
flex-direction: column;
.preset-switcher {
border: 1px solid var(--cpk-border);
border-radius: var(--cpk-radius-pill);
overflow: hidden;
background: var(--cpk-surface-elevated);
font-family: var(--cpk-font-body);

::ng-deep .mat-button-toggle-label-content {
display: inline-flex;
align-items: center;
gap: var(--cpk-space-2);
line-height: 34px;
padding: 0 var(--cpk-space-3);
}

::ng-deep .mat-button-toggle {
color: var(--cpk-text-secondary);
}

::ng-deep .mat-button-toggle-checked {
background: var(--cpk-accent-bg);
color: var(--cpk-accent);
}

::ng-deep mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}

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.

medium

Since flex-direction: column is now always applied to .workspace-container by default, the extension-mode class is no longer needed for layout styling. Please also clean up the unused isExtension signal in composer-workspace.ts and its binding in composer-workspace.ng.html to avoid dead code.

@jerelvelarde
jerelvelarde marked this pull request as ready for review July 23, 2026 12:23
@jerelvelarde
jerelvelarde force-pushed the jerel/pr2-panel-switcher branch 2 times, most recently from 0914553 to b9585b2 Compare July 27, 2026 20:32
- Three presets (Chat / Chat+Preview / Code & Engine) via a segmented
  toolbar; applyPreset() clears + rebuilds from per-preset builders
- Default fresh layout is Chat+Preview; quick-arrange keeps Google's
  freeform localStorage restore; last preset remembered for the toggle
- Retint Dockview --dv-* chrome onto --cpk-* tokens
- Tests: preset panel sets + persistence; fix a leaked getItem spy
Replace the segmented preset switcher with one IDE-style toggle that cycles
Chat -> Chat + Preview -> Code & Engine on each click, and relocate it from
the cramped workspace toolbar into the app header, between New Session and the
theme toggle. The button shows the active layout (icon + label) and its
tooltip names the next one.

Introduce a shared WorkspaceLayout service to bridge the header toggle and the
router-outlet workspace: it owns the active-preset state, cycle logic, and
localStorage persistence, while ComposerWorkspace registers a Dockview rebuild
callback while mounted (and disposes it on destroy). The toggle only renders on
the workspace route and is hidden on Gallery/Settings.
@jerelvelarde
jerelvelarde force-pushed the jerel/pr2-panel-switcher branch from b9585b2 to 5016623 Compare July 29, 2026 17:24
@jerelvelarde

Copy link
Copy Markdown
Contributor Author

Addressed the Gemini review:

  • localStorage can throw — the preset read and write are now wrapped in try/catch (in workspace-layout.ts, where the preset logic now lives). A read failure falls back to the render-focused default; a write failure is a no-op.
  • switch over if-else — layout dispatch (rebuildLayout) is now a switch whose default falls back to chat-preview rather than full.
  • Dead code — removed the unused extension-mode class binding, the isExtension signal, and the now-orphaned ngOnInit.

Rebased onto latest main; full shell suite (723 tests) green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant