tabby-tabbyspaces 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/.claude/settings.local.json +28 -2
  2. package/CHANGELOG.md +46 -20
  3. package/CLAUDE.md +163 -15
  4. package/README.md +71 -61
  5. package/RELEASE.md +91 -0
  6. package/TEST_MCP.md +176 -0
  7. package/TODO.md +72 -0
  8. package/cdp-click.js +22 -0
  9. package/cdp-test.js +28 -0
  10. package/dist/components/paneEditor.component.d.ts +6 -1
  11. package/dist/components/paneEditor.component.d.ts.map +1 -1
  12. package/dist/components/splitPreview.component.d.ts +22 -7
  13. package/dist/components/splitPreview.component.d.ts.map +1 -1
  14. package/dist/components/workspaceEditor.component.d.ts +30 -4
  15. package/dist/components/workspaceEditor.component.d.ts.map +1 -1
  16. package/dist/components/workspaceList.component.d.ts +21 -9
  17. package/dist/components/workspaceList.component.d.ts.map +1 -1
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/index.js.LICENSE.txt +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/models/workspace.model.d.ts +4 -2
  23. package/dist/models/workspace.model.d.ts.map +1 -1
  24. package/dist/package.json +26 -0
  25. package/dist/providers/settings.provider.d.ts.map +1 -1
  26. package/dist/providers/toolbar.provider.d.ts +4 -1
  27. package/dist/providers/toolbar.provider.d.ts.map +1 -1
  28. package/dist/services/startupCommand.service.d.ts +20 -0
  29. package/dist/services/startupCommand.service.d.ts.map +1 -0
  30. package/dist/services/workspaceEditor.service.d.ts +11 -3
  31. package/dist/services/workspaceEditor.service.d.ts.map +1 -1
  32. package/docs/marketing_status.md +92 -0
  33. package/package.json +2 -7
  34. package/screenshots/editor.png +0 -0
  35. package/screenshots/pane-edit.png +0 -0
  36. package/scripts/build-prod.js +39 -0
  37. package/src/components/paneEditor.component.pug +2 -2
  38. package/src/components/paneEditor.component.ts +19 -1
  39. package/src/components/splitPreview.component.pug +45 -5
  40. package/src/components/splitPreview.component.scss +79 -22
  41. package/src/components/splitPreview.component.ts +91 -16
  42. package/src/components/workspaceEditor.component.pug +130 -70
  43. package/src/components/workspaceEditor.component.scss +205 -120
  44. package/src/components/workspaceEditor.component.ts +193 -6
  45. package/src/components/workspaceList.component.pug +31 -20
  46. package/src/components/workspaceList.component.scss +12 -6
  47. package/src/components/workspaceList.component.ts +116 -34
  48. package/src/index.ts +2 -0
  49. package/src/models/workspace.model.ts +33 -6
  50. package/src/providers/settings.provider.ts +2 -2
  51. package/src/providers/toolbar.provider.ts +41 -10
  52. package/src/services/startupCommand.service.ts +142 -0
  53. package/src/services/workspaceEditor.service.ts +70 -38
  54. package/test_cdp.py +50 -0
  55. package/RELEASE_PLAN.md +0 -161
  56. package/screenshots/workspace-edit.png +0 -0
@@ -2,9 +2,9 @@ import { Component, Input, Output, EventEmitter } from '@angular/core'
2
2
  import {
3
3
  WorkspaceSplit,
4
4
  WorkspacePane,
5
+ TabbyProfile,
5
6
  isWorkspaceSplit,
6
7
  } from '../models/workspace.model'
7
- import { WorkspaceEditorService } from '../services/workspaceEditor.service'
8
8
 
9
9
  @Component({
10
10
  selector: 'split-preview',
@@ -14,16 +14,21 @@ import { WorkspaceEditorService } from '../services/workspaceEditor.service'
14
14
  export class SplitPreviewComponent {
15
15
  @Input() split!: WorkspaceSplit
16
16
  @Input() depth = 0
17
- @Output() paneClick = new EventEmitter<WorkspacePane>()
17
+ @Input() selectedPaneId: string | null = null
18
+ @Input() profiles: TabbyProfile[] = []
19
+ @Output() paneSelect = new EventEmitter<WorkspacePane>()
20
+ @Output() paneEdit = new EventEmitter<WorkspacePane>()
18
21
  @Output() splitHorizontal = new EventEmitter<WorkspacePane>()
19
22
  @Output() splitVertical = new EventEmitter<WorkspacePane>()
20
23
  @Output() removePane = new EventEmitter<WorkspacePane>()
24
+ @Output() addLeft = new EventEmitter<WorkspacePane>()
25
+ @Output() addRight = new EventEmitter<WorkspacePane>()
26
+ @Output() addTop = new EventEmitter<WorkspacePane>()
27
+ @Output() addBottom = new EventEmitter<WorkspacePane>()
21
28
 
22
29
  contextMenuPane: WorkspacePane | null = null
23
30
  contextMenuPosition = { x: 0, y: 0 }
24
31
 
25
- constructor(private workspaceService: WorkspaceEditorService) {}
26
-
27
32
  isPane(child: WorkspacePane | WorkspaceSplit): boolean {
28
33
  return !isWorkspaceSplit(child)
29
34
  }
@@ -45,7 +50,18 @@ export class SplitPreviewComponent {
45
50
  }
46
51
 
47
52
  onPaneClick(pane: WorkspacePane): void {
48
- this.paneClick.emit(pane)
53
+ this.paneSelect.emit(pane)
54
+ }
55
+
56
+ onEditClick(event: MouseEvent, pane: WorkspacePane): void {
57
+ event.stopPropagation()
58
+ this.paneEdit.emit(pane)
59
+ }
60
+
61
+ truncate(text: string, maxLength: number): string {
62
+ return text.length > maxLength
63
+ ? text.substring(0, maxLength) + '...'
64
+ : text
49
65
  }
50
66
 
51
67
  onContextMenu(event: MouseEvent, pane: WorkspacePane): void {
@@ -58,6 +74,13 @@ export class SplitPreviewComponent {
58
74
  this.contextMenuPane = null
59
75
  }
60
76
 
77
+ onEdit(): void {
78
+ if (this.contextMenuPane) {
79
+ this.paneEdit.emit(this.contextMenuPane)
80
+ this.closeContextMenu()
81
+ }
82
+ }
83
+
61
84
  onSplitH(): void {
62
85
  if (this.contextMenuPane) {
63
86
  this.splitHorizontal.emit(this.contextMenuPane)
@@ -72,29 +95,65 @@ export class SplitPreviewComponent {
72
95
  }
73
96
  }
74
97
 
75
- onRemove(): void {
98
+ onAddLeft(): void {
76
99
  if (this.contextMenuPane) {
77
- this.removePane.emit(this.contextMenuPane)
100
+ this.addLeft.emit(this.contextMenuPane)
78
101
  this.closeContextMenu()
79
102
  }
80
103
  }
81
104
 
82
- getProfileName(profileId: string): string {
83
- return this.workspaceService.getProfileName(profileId) ?? 'Select profile'
105
+ onAddRight(): void {
106
+ if (this.contextMenuPane) {
107
+ this.addRight.emit(this.contextMenuPane)
108
+ this.closeContextMenu()
109
+ }
110
+ }
111
+
112
+ onAddTop(): void {
113
+ if (this.contextMenuPane) {
114
+ this.addTop.emit(this.contextMenuPane)
115
+ this.closeContextMenu()
116
+ }
117
+ }
118
+
119
+ onAddBottom(): void {
120
+ if (this.contextMenuPane) {
121
+ this.addBottom.emit(this.contextMenuPane)
122
+ this.closeContextMenu()
123
+ }
124
+ }
125
+
126
+ onRemove(): void {
127
+ if (this.contextMenuPane) {
128
+ this.removePane.emit(this.contextMenuPane)
129
+ this.closeContextMenu()
130
+ }
84
131
  }
85
132
 
86
133
  getPaneLabel(pane: WorkspacePane): string {
87
- if (pane.title) return pane.title
88
- if (pane.startupCommand) {
89
- const cmd = pane.startupCommand
90
- return cmd.length > 20 ? cmd.substring(0, 17) + '...' : cmd
134
+ // Base label is always the profile name
135
+ let profileName = ''
136
+ if (pane.profileId) {
137
+ const profile = this.profiles.find(p => p.id === pane.profileId)
138
+ if (profile?.name) profileName = profile.name
139
+ }
140
+
141
+ if (!profileName) return 'Select profile'
142
+
143
+ // Format: "Title - Profile" or just "Profile"
144
+ if (pane.title) {
145
+ return `${pane.title} - ${profileName}`
91
146
  }
92
- return this.getProfileName(pane.profileId)
147
+ return profileName
93
148
  }
94
149
 
95
150
  // Pass-through events from nested splits
96
- onNestedPaneClick(pane: WorkspacePane): void {
97
- this.paneClick.emit(pane)
151
+ onNestedPaneSelect(pane: WorkspacePane): void {
152
+ this.paneSelect.emit(pane)
153
+ }
154
+
155
+ onNestedPaneEdit(pane: WorkspacePane): void {
156
+ this.paneEdit.emit(pane)
98
157
  }
99
158
 
100
159
  onNestedSplitH(pane: WorkspacePane): void {
@@ -105,6 +164,22 @@ export class SplitPreviewComponent {
105
164
  this.splitVertical.emit(pane)
106
165
  }
107
166
 
167
+ onNestedAddLeft(pane: WorkspacePane): void {
168
+ this.addLeft.emit(pane)
169
+ }
170
+
171
+ onNestedAddRight(pane: WorkspacePane): void {
172
+ this.addRight.emit(pane)
173
+ }
174
+
175
+ onNestedAddTop(pane: WorkspacePane): void {
176
+ this.addTop.emit(pane)
177
+ }
178
+
179
+ onNestedAddBottom(pane: WorkspacePane): void {
180
+ this.addBottom.emit(pane)
181
+ }
182
+
108
183
  onNestedRemove(pane: WorkspacePane): void {
109
184
  this.removePane.emit(pane)
110
185
  }
@@ -1,83 +1,143 @@
1
- .workspace-editor-overlay((click)='onCancel()')
2
- .workspace-editor-modal((click)='$event.stopPropagation()')
3
- .modal-header
4
- h4 {{ workspace.id ? 'Edit' : 'New' }} Workspace
5
- button.btn.btn-link.close-btn(type='button', (click)='onCancel()')
6
- i.fas.fa-times
1
+ //- Inline workspace editor
2
+ .workspace-editor-inline
3
+ .editor-body
4
+ ng-container(*ngTemplateOutlet='editorContent')
7
5
 
8
- .modal-body
9
- .form-row
10
- .form-group.flex-grow
11
- label Name
12
- input.form-control(
13
- type='text',
14
- [(ngModel)]='workspace.name',
15
- placeholder='Workspace name'
16
- )
6
+ .editor-footer
7
+ label.startup-checkbox
8
+ input(type='checkbox', [(ngModel)]='workspace.launchOnStartup')
9
+ | Launch on startup
10
+ .editor-actions
11
+ button.btn.btn-secondary.btn-sm(type='button', (click)='onCancel()') Cancel
12
+ button.btn.btn-primary.btn-sm(type='button', (click)='onSave()', [disabled]='!workspace.name?.trim() || !hasUnsavedChanges')
13
+ i.fas.fa-save
14
+ | Save
15
+ span.unsaved-indicator(*ngIf='hasUnsavedChanges') *
16
+
17
+ //- Shared editor content
18
+ ng-template(#editorContent)
19
+ //- Top row: Color picker, Name input, Icon grid
20
+ .editor-top-row
21
+ .color-picker
22
+ input.color-input(
23
+ type='color',
24
+ [(ngModel)]='workspace.color',
25
+ title='Workspace color'
26
+ )
17
27
 
18
- .form-row
19
- .form-group
20
- label Icon
21
- .icon-selector
22
- button.icon-option(
23
- *ngFor='let icon of availableIcons',
24
- type='button',
25
- [class.selected]='workspace.icon === icon',
26
- (click)='workspace.icon = icon'
27
- )
28
- i.fas([class]='"fa-" + icon')
28
+ input.name-input(
29
+ #nameInput,
30
+ type='text',
31
+ [(ngModel)]='workspace.name',
32
+ placeholder='Name your workspace'
33
+ )
29
34
 
30
- .form-group
31
- label Color
32
- input.form-control.color-input(
33
- type='color',
34
- [(ngModel)]='workspace.color'
35
- )
35
+ .icon-picker
36
+ button.icon-trigger(
37
+ type='button',
38
+ [style.color]='workspace.color',
39
+ (click)='toggleIconDropdown()',
40
+ title='Select icon'
41
+ )
42
+ i.fas([class]='"fa-" + workspace.icon')
36
43
 
37
- .form-row
38
- .form-group
39
- label
40
- input(type='checkbox', [(ngModel)]='workspace.isDefault')
41
- | Launch on startup
44
+ .icon-dropdown(*ngIf='iconDropdownOpen')
45
+ button.icon-option(
46
+ *ngFor='let icon of availableIcons',
47
+ type='button',
48
+ [class.selected]='workspace.icon === icon',
49
+ [style.color]='workspace.icon === icon ? workspace.color : null',
50
+ (click)='selectIcon(icon)'
51
+ )
52
+ i.fas([class]='"fa-" + icon')
42
53
 
43
- .form-section
44
- .section-header
45
- h5 Layout Preview
46
- .orientation-toggle
47
- button.btn.btn-sm(
48
- type='button',
49
- [class.btn-primary]='workspace.root.orientation === "horizontal"',
50
- [class.btn-outline-secondary]='workspace.root.orientation !== "horizontal"',
51
- (click)='setOrientation("horizontal")'
52
- )
53
- i.fas.fa-arrows-alt-h
54
- | Horizontal
55
- button.btn.btn-sm(
56
- type='button',
57
- [class.btn-primary]='workspace.root.orientation === "vertical"',
58
- [class.btn-outline-secondary]='workspace.root.orientation !== "vertical"',
59
- (click)='setOrientation("vertical")'
60
- )
61
- i.fas.fa-arrows-alt-v
62
- | Vertical
54
+ //- Layout section
55
+ .layout-section
56
+ .layout-header
57
+ span.layout-title Layout
63
58
 
64
- split-preview(
65
- [split]='workspace.root',
66
- (paneClick)='selectPane($event)',
67
- (splitHorizontal)='splitPane($event, "horizontal")',
68
- (splitVertical)='splitPane($event, "vertical")',
69
- (removePane)='removePane($event)'
59
+ .layout-toolbar
60
+ .toolbar-group.split-group
61
+ span.toolbar-label Split
62
+ button.toolbar-btn(
63
+ type='button',
64
+ [disabled]='!selectedPaneId',
65
+ (click)='splitSelectedPane("horizontal")',
66
+ title='Split Horizontal'
67
+ )
68
+ i.fas.fa-arrows-alt-h
69
+ button.toolbar-btn(
70
+ type='button',
71
+ [disabled]='!selectedPaneId',
72
+ (click)='splitSelectedPane("vertical")',
73
+ title='Split Vertical'
70
74
  )
75
+ i.fas.fa-arrows-alt-v
71
76
 
72
- .modal-footer
73
- button.btn.btn-secondary(type='button', (click)='onCancel()') Cancel
74
- button.btn.btn-primary(type='button', (click)='onSave()', [disabled]='!workspace.name?.trim()')
75
- i.fas.fa-save
76
- | Save Workspace
77
+ .toolbar-divider
78
+
79
+ .toolbar-group.add-group
80
+ span.toolbar-label Add
81
+ button.toolbar-btn(
82
+ type='button',
83
+ [disabled]='!selectedPaneId',
84
+ (click)='addPane("left")',
85
+ title='Add Left'
86
+ )
87
+ i.fas.fa-caret-left
88
+ button.toolbar-btn(
89
+ type='button',
90
+ [disabled]='!selectedPaneId',
91
+ (click)='addPane("right")',
92
+ title='Add Right'
93
+ )
94
+ i.fas.fa-caret-right
95
+ button.toolbar-btn(
96
+ type='button',
97
+ [disabled]='!selectedPaneId',
98
+ (click)='addPane("top")',
99
+ title='Add Top'
100
+ )
101
+ i.fas.fa-caret-up
102
+ button.toolbar-btn(
103
+ type='button',
104
+ [disabled]='!selectedPaneId',
105
+ (click)='addPane("bottom")',
106
+ title='Add Bottom'
107
+ )
108
+ i.fas.fa-caret-down
109
+
110
+ .toolbar-spacer
111
+
112
+ .toolbar-group
113
+ button.toolbar-btn.danger(
114
+ type='button',
115
+ [disabled]='!selectedPaneId || !canRemovePane()',
116
+ (click)='removeSelectedPane()',
117
+ title='Remove pane'
118
+ )
119
+ i.fas.fa-trash
120
+
121
+ .layout-preview-container((click)='onPreviewBackgroundClick()')
122
+ split-preview(
123
+ [split]='workspace.root',
124
+ [selectedPaneId]='selectedPaneId',
125
+ [profiles]='profiles',
126
+ (paneSelect)='selectPane($event)',
127
+ (paneEdit)='editPane($event)',
128
+ (splitHorizontal)='splitPane($event, "horizontal")',
129
+ (splitVertical)='splitPane($event, "vertical")',
130
+ (addLeft)='addPaneFromEvent($event, "left")',
131
+ (addRight)='addPaneFromEvent($event, "right")',
132
+ (addTop)='addPaneFromEvent($event, "top")',
133
+ (addBottom)='addPaneFromEvent($event, "bottom")',
134
+ (removePane)='removePane($event)'
135
+ )
77
136
 
137
+ //- Pane editor (always modal)
78
138
  pane-editor(
79
- *ngIf='showPaneEditor && selectedPane',
80
- [pane]='selectedPane',
139
+ *ngIf='showPaneEditor && editingPane',
140
+ [pane]='editingPane',
81
141
  [profiles]='profiles',
82
142
  (save)='onPaneSave($event)',
83
143
  (cancel)='closePaneEditor()'