tabby-tabbyspaces 0.0.1 → 0.2.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.
- package/.claude/settings.local.json +29 -2
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +81 -0
- package/.github/workflows/release.yml +30 -0
- package/CHANGELOG.md +92 -20
- package/CLAUDE.md +196 -15
- package/CONTRIBUTING.md +3 -1
- package/README.md +80 -61
- package/RELEASE.md +91 -0
- package/TODO.md +77 -0
- package/dist/build-config.d.ts +3 -3
- package/dist/components/deleteConfirmModal.component.d.ts +7 -0
- package/dist/components/deleteConfirmModal.component.d.ts.map +1 -0
- package/dist/components/paneEditor.component.d.ts +9 -13
- package/dist/components/paneEditor.component.d.ts.map +1 -1
- package/dist/components/splitPreview.component.d.ts +50 -35
- package/dist/components/splitPreview.component.d.ts.map +1 -1
- package/dist/components/workspaceEditor.component.d.ts +61 -28
- package/dist/components/workspaceEditor.component.d.ts.map +1 -1
- package/dist/components/workspaceList.component.d.ts +56 -27
- package/dist/components/workspaceList.component.d.ts.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.LICENSE.txt +1 -1
- package/dist/index.js.map +1 -1
- package/dist/models/workspace.model.d.ts +118 -76
- package/dist/models/workspace.model.d.ts.map +1 -1
- package/dist/package.json +26 -0
- package/dist/providers/config.provider.d.ts +8 -8
- package/dist/providers/settings.provider.d.ts +7 -7
- package/dist/providers/settings.provider.d.ts.map +1 -1
- package/dist/providers/toolbar.provider.d.ts +23 -12
- package/dist/providers/toolbar.provider.d.ts.map +1 -1
- package/dist/services/startupCommand.service.d.ts +28 -0
- package/dist/services/startupCommand.service.d.ts.map +1 -0
- package/dist/services/workspaceBackground.service.d.ts +38 -0
- package/dist/services/workspaceBackground.service.d.ts.map +1 -0
- package/dist/services/workspaceEditor.service.d.ts +46 -24
- package/dist/services/workspaceEditor.service.d.ts.map +1 -1
- package/docs/DESIGN.md +57 -0
- package/docs/SESSION-2026-01-14-S1-DESIGN.md +134 -0
- package/docs/marketing_status.md +92 -0
- package/mockups/index.html +162 -0
- package/mockups/s1-tight-sharp.html +522 -0
- package/mockups/shared/base.css +216 -0
- package/mockups/v06-tabbed.html +643 -0
- package/package.json +3 -7
- package/screenshots/editor.png +0 -0
- package/screenshots/pane-edit.png +0 -0
- package/scripts/build-dev.js +2 -1
- package/scripts/build-prod.js +40 -0
- package/src/components/deleteConfirmModal.component.ts +23 -0
- package/src/components/paneEditor.component.pug +27 -43
- package/src/components/paneEditor.component.scss +37 -85
- package/src/components/paneEditor.component.ts +6 -16
- package/src/components/splitPreview.component.pug +36 -5
- package/src/components/splitPreview.component.scss +78 -45
- package/src/components/splitPreview.component.ts +83 -18
- package/src/components/workspaceEditor.component.pug +162 -74
- package/src/components/workspaceEditor.component.scss +261 -108
- package/src/components/workspaceEditor.component.ts +294 -31
- package/src/components/workspaceList.component.pug +32 -41
- package/src/components/workspaceList.component.scss +89 -74
- package/src/components/workspaceList.component.ts +181 -44
- package/src/index.ts +6 -0
- package/src/models/workspace.model.ts +113 -8
- package/src/providers/settings.provider.ts +2 -2
- package/src/providers/toolbar.provider.ts +113 -13
- package/src/services/startupCommand.service.ts +140 -0
- package/src/services/workspaceBackground.service.ts +167 -0
- package/src/services/workspaceEditor.service.ts +134 -65
- package/src/styles/_index.scss +3 -0
- package/src/styles/_mixins.scss +180 -0
- package/src/styles/_variables.scss +67 -0
- package/RELEASE_PLAN.md +0 -161
- package/screenshots/workspace-edit.png +0 -0
|
@@ -1,28 +1,41 @@
|
|
|
1
|
-
import { Component, Input, Output, EventEmitter } from '@angular/core'
|
|
1
|
+
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChanges, ChangeDetectorRef } 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',
|
|
11
11
|
template: require('./splitPreview.component.pug'),
|
|
12
12
|
styles: [require('./splitPreview.component.scss')],
|
|
13
13
|
})
|
|
14
|
-
export class SplitPreviewComponent {
|
|
14
|
+
export class SplitPreviewComponent implements OnChanges {
|
|
15
15
|
@Input() split!: WorkspaceSplit
|
|
16
16
|
@Input() depth = 0
|
|
17
|
-
@
|
|
17
|
+
@Input() selectedPaneId: string | null = null
|
|
18
|
+
@Input() profiles: TabbyProfile[] = []
|
|
19
|
+
@Output() paneEdit = new EventEmitter<WorkspacePane>()
|
|
18
20
|
@Output() splitHorizontal = new EventEmitter<WorkspacePane>()
|
|
19
21
|
@Output() splitVertical = new EventEmitter<WorkspacePane>()
|
|
20
22
|
@Output() removePane = new EventEmitter<WorkspacePane>()
|
|
23
|
+
@Output() addLeft = new EventEmitter<WorkspacePane>()
|
|
24
|
+
@Output() addRight = new EventEmitter<WorkspacePane>()
|
|
25
|
+
@Output() addTop = new EventEmitter<WorkspacePane>()
|
|
26
|
+
@Output() addBottom = new EventEmitter<WorkspacePane>()
|
|
21
27
|
|
|
22
28
|
contextMenuPane: WorkspacePane | null = null
|
|
23
29
|
contextMenuPosition = { x: 0, y: 0 }
|
|
24
30
|
|
|
25
|
-
constructor(private
|
|
31
|
+
constructor(private cdr: ChangeDetectorRef) {}
|
|
32
|
+
|
|
33
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
34
|
+
// Clear context menu when split input changes to avoid stale state
|
|
35
|
+
if (changes['split']) {
|
|
36
|
+
this.closeContextMenu()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
26
39
|
|
|
27
40
|
isPane(child: WorkspacePane | WorkspaceSplit): boolean {
|
|
28
41
|
return !isWorkspaceSplit(child)
|
|
@@ -45,7 +58,13 @@ export class SplitPreviewComponent {
|
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
onPaneClick(pane: WorkspacePane): void {
|
|
48
|
-
this.
|
|
61
|
+
this.paneEdit.emit(pane)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
truncate(text: string, maxLength: number): string {
|
|
65
|
+
return text.length > maxLength
|
|
66
|
+
? text.substring(0, maxLength) + '...'
|
|
67
|
+
: text
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
onContextMenu(event: MouseEvent, pane: WorkspacePane): void {
|
|
@@ -56,6 +75,14 @@ export class SplitPreviewComponent {
|
|
|
56
75
|
|
|
57
76
|
closeContextMenu(): void {
|
|
58
77
|
this.contextMenuPane = null
|
|
78
|
+
this.cdr.detectChanges()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onEdit(): void {
|
|
82
|
+
if (this.contextMenuPane) {
|
|
83
|
+
this.paneEdit.emit(this.contextMenuPane)
|
|
84
|
+
this.closeContextMenu()
|
|
85
|
+
}
|
|
59
86
|
}
|
|
60
87
|
|
|
61
88
|
onSplitH(): void {
|
|
@@ -72,29 +99,51 @@ export class SplitPreviewComponent {
|
|
|
72
99
|
}
|
|
73
100
|
}
|
|
74
101
|
|
|
75
|
-
|
|
102
|
+
onAddLeft(): void {
|
|
76
103
|
if (this.contextMenuPane) {
|
|
77
|
-
this.
|
|
104
|
+
this.addLeft.emit(this.contextMenuPane)
|
|
78
105
|
this.closeContextMenu()
|
|
79
106
|
}
|
|
80
107
|
}
|
|
81
108
|
|
|
82
|
-
|
|
83
|
-
|
|
109
|
+
onAddRight(): void {
|
|
110
|
+
if (this.contextMenuPane) {
|
|
111
|
+
this.addRight.emit(this.contextMenuPane)
|
|
112
|
+
this.closeContextMenu()
|
|
113
|
+
}
|
|
84
114
|
}
|
|
85
115
|
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return cmd.length > 20 ? cmd.substring(0, 17) + '...' : cmd
|
|
116
|
+
onAddTop(): void {
|
|
117
|
+
if (this.contextMenuPane) {
|
|
118
|
+
this.addTop.emit(this.contextMenuPane)
|
|
119
|
+
this.closeContextMenu()
|
|
91
120
|
}
|
|
92
|
-
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onAddBottom(): void {
|
|
124
|
+
if (this.contextMenuPane) {
|
|
125
|
+
this.addBottom.emit(this.contextMenuPane)
|
|
126
|
+
this.closeContextMenu()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onRemove(): void {
|
|
131
|
+
if (this.contextMenuPane) {
|
|
132
|
+
this.removePane.emit(this.contextMenuPane)
|
|
133
|
+
this.closeContextMenu()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
getPaneLabel(pane: WorkspacePane): string {
|
|
138
|
+
if (!pane.profileId) return 'Select profile'
|
|
139
|
+
|
|
140
|
+
const profile = this.profiles.find(p => p.id === pane.profileId)
|
|
141
|
+
return profile?.name || 'Select profile'
|
|
93
142
|
}
|
|
94
143
|
|
|
95
144
|
// Pass-through events from nested splits
|
|
96
|
-
|
|
97
|
-
this.
|
|
145
|
+
onNestedPaneEdit(pane: WorkspacePane): void {
|
|
146
|
+
this.paneEdit.emit(pane)
|
|
98
147
|
}
|
|
99
148
|
|
|
100
149
|
onNestedSplitH(pane: WorkspacePane): void {
|
|
@@ -105,6 +154,22 @@ export class SplitPreviewComponent {
|
|
|
105
154
|
this.splitVertical.emit(pane)
|
|
106
155
|
}
|
|
107
156
|
|
|
157
|
+
onNestedAddLeft(pane: WorkspacePane): void {
|
|
158
|
+
this.addLeft.emit(pane)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
onNestedAddRight(pane: WorkspacePane): void {
|
|
162
|
+
this.addRight.emit(pane)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
onNestedAddTop(pane: WorkspacePane): void {
|
|
166
|
+
this.addTop.emit(pane)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
onNestedAddBottom(pane: WorkspacePane): void {
|
|
170
|
+
this.addBottom.emit(pane)
|
|
171
|
+
}
|
|
172
|
+
|
|
108
173
|
onNestedRemove(pane: WorkspacePane): void {
|
|
109
174
|
this.removePane.emit(pane)
|
|
110
175
|
}
|
|
@@ -1,84 +1,172 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
i.fas.fa-times
|
|
1
|
+
//- Section 1: Workspace Settings
|
|
2
|
+
.editor-section
|
|
3
|
+
.section-title
|
|
4
|
+
i.fas.fa-cog
|
|
5
|
+
| Workspace Settings
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
.form-row
|
|
8
|
+
.form-group
|
|
9
|
+
label Name
|
|
10
|
+
input.form-control(
|
|
11
|
+
#nameInput,
|
|
12
|
+
type='text',
|
|
13
|
+
[(ngModel)]='workspace.name',
|
|
14
|
+
placeholder='Workspace name'
|
|
15
|
+
)
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
[(ngModel)]='workspace.color'
|
|
35
|
-
)
|
|
17
|
+
.form-group.auto-width
|
|
18
|
+
label Icon
|
|
19
|
+
.dropdown-trigger((click)='toggleIconDropdown()')
|
|
20
|
+
span.dropdown-icon([style.color]='workspace.color')
|
|
21
|
+
i.fas([class]='"fa-" + workspace.icon')
|
|
22
|
+
span.dropdown-chevron
|
|
23
|
+
i.fas.fa-chevron-down
|
|
24
|
+
.icon-dropdown(*ngIf='iconDropdownOpen')
|
|
25
|
+
button.icon-option(
|
|
26
|
+
*ngFor='let icon of availableIcons',
|
|
27
|
+
type='button',
|
|
28
|
+
[class.selected]='workspace.icon === icon',
|
|
29
|
+
[style.color]='workspace.icon === icon ? workspace.color : null',
|
|
30
|
+
(click)='selectIcon(icon)'
|
|
31
|
+
)
|
|
32
|
+
i.fas([class]='"fa-" + icon')
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
.form-group.auto-width
|
|
35
|
+
label Color
|
|
36
|
+
.color-trigger
|
|
37
|
+
span.dropdown-color([style.background]='workspace.color')
|
|
38
|
+
input.color-input-hidden(
|
|
39
|
+
type='color',
|
|
40
|
+
[(ngModel)]='workspace.color'
|
|
41
|
+
)
|
|
42
|
+
span.dropdown-chevron
|
|
43
|
+
i.fas.fa-chevron-down
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
.form-group.auto-width
|
|
46
|
+
label Background
|
|
47
|
+
.background-picker
|
|
48
|
+
.background-trigger((click)='toggleBackgroundDropdown()')
|
|
49
|
+
span.background-preview([style.background]='workspace.background?.value || "transparent"')
|
|
50
|
+
i.fas.fa-ban(*ngIf='!workspace.background?.value')
|
|
51
|
+
span.dropdown-chevron
|
|
52
|
+
i.fas.fa-chevron-down
|
|
53
|
+
.background-dropdown(*ngIf='backgroundDropdownOpen')
|
|
54
|
+
.preset-grid
|
|
55
|
+
button.preset-option(
|
|
56
|
+
*ngFor='let preset of backgroundPresets',
|
|
48
57
|
type='button',
|
|
49
|
-
[class.
|
|
50
|
-
[
|
|
51
|
-
(click)='
|
|
58
|
+
[class.selected]='isBackgroundSelected(preset)',
|
|
59
|
+
[style.background]='preset.value || "var(--theme-bg)"',
|
|
60
|
+
(click)='selectBackgroundPreset(preset)'
|
|
52
61
|
)
|
|
53
|
-
i.fas.fa-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
type='
|
|
57
|
-
[
|
|
58
|
-
|
|
59
|
-
(
|
|
62
|
+
i.fas.fa-ban(*ngIf='preset.type === "none"')
|
|
63
|
+
.custom-input-wrapper
|
|
64
|
+
input.form-control.custom-bg-input(
|
|
65
|
+
type='text',
|
|
66
|
+
[(ngModel)]='customBackgroundValue',
|
|
67
|
+
placeholder='Custom CSS gradient...',
|
|
68
|
+
(blur)='applyCustomBackground()'
|
|
60
69
|
)
|
|
61
|
-
i.fas.fa-arrows-alt-v
|
|
62
|
-
| Vertical
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
//- Section 2: Split Layout
|
|
72
|
+
.editor-section
|
|
73
|
+
.section-title
|
|
74
|
+
i.fas.fa-columns
|
|
75
|
+
| Split Layout
|
|
76
|
+
|
|
77
|
+
.split-preview-container
|
|
78
|
+
//- Preview toolbar
|
|
79
|
+
.preview-toolbar
|
|
80
|
+
button.preview-btn(
|
|
81
|
+
type='button',
|
|
82
|
+
title='Split Horizontal',
|
|
83
|
+
[disabled]='!selectedPaneId',
|
|
84
|
+
(click)='splitSelectedPane("horizontal")'
|
|
85
|
+
)
|
|
86
|
+
i.fas.fa-grip-lines-vertical
|
|
87
|
+
button.preview-btn(
|
|
88
|
+
type='button',
|
|
89
|
+
title='Split Vertical',
|
|
90
|
+
[disabled]='!selectedPaneId',
|
|
91
|
+
(click)='splitSelectedPane("vertical")'
|
|
92
|
+
)
|
|
93
|
+
i.fas.fa-grip-lines
|
|
94
|
+
span.toolbar-separator
|
|
95
|
+
button.preview-btn(
|
|
96
|
+
type='button',
|
|
97
|
+
title='Add Left',
|
|
98
|
+
[disabled]='!selectedPaneId',
|
|
99
|
+
(click)='addPane("left")'
|
|
100
|
+
)
|
|
101
|
+
i.fas.fa-arrow-left
|
|
102
|
+
button.preview-btn(
|
|
103
|
+
type='button',
|
|
104
|
+
title='Add Right',
|
|
105
|
+
[disabled]='!selectedPaneId',
|
|
106
|
+
(click)='addPane("right")'
|
|
107
|
+
)
|
|
108
|
+
i.fas.fa-arrow-right
|
|
109
|
+
button.preview-btn(
|
|
110
|
+
type='button',
|
|
111
|
+
title='Add Top',
|
|
112
|
+
[disabled]='!selectedPaneId',
|
|
113
|
+
(click)='addPane("top")'
|
|
114
|
+
)
|
|
115
|
+
i.fas.fa-arrow-up
|
|
116
|
+
button.preview-btn(
|
|
117
|
+
type='button',
|
|
118
|
+
title='Add Bottom',
|
|
119
|
+
[disabled]='!selectedPaneId',
|
|
120
|
+
(click)='addPane("bottom")'
|
|
121
|
+
)
|
|
122
|
+
i.fas.fa-arrow-down
|
|
123
|
+
span.toolbar-separator
|
|
124
|
+
button.preview-btn.danger(
|
|
125
|
+
type='button',
|
|
126
|
+
title='Remove pane',
|
|
127
|
+
[disabled]='!selectedPaneId || !canRemovePane()',
|
|
128
|
+
(click)='removeSelectedPane()'
|
|
129
|
+
)
|
|
130
|
+
i.fas.fa-trash
|
|
131
|
+
|
|
132
|
+
//- Split preview
|
|
133
|
+
.layout-preview((click)='onPreviewBackgroundClick()')
|
|
134
|
+
split-preview(
|
|
135
|
+
[split]='workspace.root',
|
|
136
|
+
[selectedPaneId]='selectedPaneId',
|
|
137
|
+
[profiles]='profiles',
|
|
138
|
+
(paneEdit)='editPane($event)',
|
|
139
|
+
(splitHorizontal)='splitPane($event, "horizontal")',
|
|
140
|
+
(splitVertical)='splitPane($event, "vertical")',
|
|
141
|
+
(addLeft)='addPaneFromEvent($event, "left")',
|
|
142
|
+
(addRight)='addPaneFromEvent($event, "right")',
|
|
143
|
+
(addTop)='addPaneFromEvent($event, "top")',
|
|
144
|
+
(addBottom)='addPaneFromEvent($event, "bottom")',
|
|
145
|
+
(removePane)='removePane($event)'
|
|
146
|
+
)
|
|
71
147
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
148
|
+
//- Inline pane editor
|
|
149
|
+
pane-editor(
|
|
150
|
+
*ngIf='showPaneEditor && editingPane',
|
|
151
|
+
[pane]='editingPane',
|
|
152
|
+
[profiles]='profiles',
|
|
153
|
+
(close)='closePaneEditor()'
|
|
154
|
+
)
|
|
77
155
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
)
|
|
156
|
+
//- Action buttons
|
|
157
|
+
.action-buttons
|
|
158
|
+
.checkbox-group
|
|
159
|
+
input(type='checkbox', [(ngModel)]='workspace.launchOnStartup', id='launchStartup')
|
|
160
|
+
label(for='launchStartup') Launch on startup
|
|
161
|
+
.action-buttons-right
|
|
162
|
+
button.btn.btn-ghost(type='button', (click)='onCancel()')
|
|
163
|
+
i.fas.fa-xmark
|
|
164
|
+
| Cancel
|
|
165
|
+
button.btn.btn-success(
|
|
166
|
+
type='button',
|
|
167
|
+
(click)='onSave()',
|
|
168
|
+
[disabled]='!workspace.name?.trim() || !hasUnsavedChanges'
|
|
169
|
+
)
|
|
170
|
+
i.fas.fa-check
|
|
171
|
+
| Save
|
|
172
|
+
span.unsaved-indicator(*ngIf='hasUnsavedChanges') *
|