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
@@ -11,22 +11,31 @@ import {
11
11
  TabbySplitLayoutProfile,
12
12
  } from '../models/workspace.model'
13
13
  import { CONFIG_KEY, DISPLAY_NAME } from '../build-config'
14
+ import { PendingCommand } from './startupCommand.service'
14
15
 
15
16
  @Injectable({ providedIn: 'root' })
16
17
  export class WorkspaceEditorService {
18
+ private cachedProfiles: TabbyProfile[] = []
19
+
17
20
  constructor(
18
21
  private config: ConfigService,
19
22
  private notifications: NotificationsService,
20
23
  private profilesService: ProfilesService
21
24
  ) {}
22
25
 
26
+ private async cacheProfiles(): Promise<void> {
27
+ this.cachedProfiles = (await this.profilesService.getProfiles()) as TabbyProfile[]
28
+ }
29
+
23
30
  getWorkspaces(): Workspace[] {
24
- return this.config.store[CONFIG_KEY]?.workspaces ?? []
31
+ return this.config.store?.[CONFIG_KEY]?.workspaces ?? []
25
32
  }
26
33
 
27
34
  async saveWorkspaces(workspaces: Workspace[]): Promise<boolean> {
35
+ if (!this.config.store?.[CONFIG_KEY]) {
36
+ return false
37
+ }
28
38
  this.config.store[CONFIG_KEY].workspaces = workspaces
29
- this.syncTabbyProfiles(workspaces)
30
39
  return await this.saveConfig()
31
40
  }
32
41
 
@@ -60,28 +69,32 @@ export class WorkspaceEditorService {
60
69
  async getAvailableProfiles(): Promise<TabbyProfile[]> {
61
70
  const allProfiles = await this.profilesService.getProfiles()
62
71
  return allProfiles.filter(
63
- (p) => p.type === 'local' && !p.id?.startsWith('split-layout:')
72
+ (p) =>
73
+ (p.type === 'local' || p.type?.startsWith('local:')) &&
74
+ !p.id?.startsWith('split-layout:')
64
75
  ) as TabbyProfile[]
65
76
  }
66
77
 
67
- private syncTabbyProfiles(workspaces: Workspace[]): void {
68
- const profiles: (TabbyProfile | TabbySplitLayoutProfile)[] = this.config.store.profiles ?? []
69
-
70
- // Remove old plugin profiles (mutate in place)
71
- for (let i = profiles.length - 1; i >= 0; i--) {
72
- if (profiles[i].id?.startsWith(`split-layout:${CONFIG_KEY}:`)) {
73
- profiles.splice(i, 1)
74
- }
78
+ /**
79
+ * Cleanup orphaned profiles from previous plugin versions.
80
+ * Call this once on plugin init.
81
+ */
82
+ cleanupOrphanedProfiles(): void {
83
+ if (!this.config.store?.profiles) {
84
+ return
75
85
  }
76
-
77
- // Add new workspace profiles
78
- for (const workspace of workspaces) {
79
- const tabbyProfile = this.generateTabbyProfile(workspace)
80
- profiles.push(tabbyProfile)
86
+ const profiles: TabbyProfile[] = this.config.store.profiles
87
+ const prefix = `split-layout:${CONFIG_KEY}:`
88
+ const filtered = profiles.filter((p) => !p.id?.startsWith(prefix))
89
+ if (filtered.length !== profiles.length) {
90
+ this.config.store.profiles = filtered
91
+ this.config.save()
92
+ console.log(`[${DISPLAY_NAME}] Cleaned up ${profiles.length - filtered.length} orphaned profiles`)
81
93
  }
82
94
  }
83
95
 
84
- generateTabbyProfile(workspace: Workspace): TabbySplitLayoutProfile {
96
+ async generateTabbyProfile(workspace: Workspace): Promise<TabbySplitLayoutProfile> {
97
+ await this.cacheProfiles()
85
98
  const safeName = this.sanitizeForProfileId(workspace.name)
86
99
  return {
87
100
  id: `split-layout:${CONFIG_KEY}:${safeName}:${workspace.id}`,
@@ -138,19 +151,8 @@ export class WorkspaceEditorService {
138
151
  runAsAdministrator: false,
139
152
  }
140
153
 
141
- // Handle startup command for different shells
142
- if (pane.startupCommand) {
143
- const cmd = baseProfile.options?.command || ''
144
- if (cmd.includes('nu.exe') || baseProfile.name?.toLowerCase().includes('nushell')) {
145
- options.args = ['-e', pane.startupCommand]
146
- } else if (cmd.includes('powershell') || cmd.includes('pwsh')) {
147
- options.args = ['-NoExit', '-Command', pane.startupCommand]
148
- } else if (cmd.includes('cmd.exe')) {
149
- options.args = ['/K', pane.startupCommand]
150
- } else {
151
- options.args = ['-c', pane.startupCommand]
152
- }
153
- }
154
+ // Note: startupCommand is handled via sendInput() in StartupCommandService
155
+ // to avoid re-execution when Tabby splits the pane
154
156
 
155
157
  const profile = {
156
158
  id: baseProfile.id,
@@ -168,13 +170,17 @@ export class WorkspaceEditorService {
168
170
  behaviorOnSessionEnd: 'auto',
169
171
  }
170
172
 
173
+ // Use pane.id for matching in StartupCommandService
174
+ // Original title will be restored after command execution
175
+ const cwd = pane.cwd || baseProfile.options?.cwd || ''
171
176
  return {
172
177
  type: 'app:local-tab',
173
178
  profile,
174
179
  savedState: false,
175
- tabTitle: pane.title || '',
176
- tabCustomTitle: pane.title || '',
177
- disableDynamicTitle: !!pane.title,
180
+ tabTitle: pane.id,
181
+ tabCustomTitle: pane.id,
182
+ disableDynamicTitle: false,
183
+ cwd,
178
184
  }
179
185
  }
180
186
 
@@ -182,7 +188,7 @@ export class WorkspaceEditorService {
182
188
  const clone = JSON.parse(JSON.stringify(workspace)) as Workspace
183
189
  clone.id = generateUUID()
184
190
  clone.name = `${workspace.name} (Copy)`
185
- clone.isDefault = false
191
+ clone.launchOnStartup = false
186
192
  this.regenerateIds(clone.root)
187
193
  return clone
188
194
  }
@@ -207,12 +213,38 @@ export class WorkspaceEditorService {
207
213
  }
208
214
 
209
215
  private getProfileById(profileId: string): TabbyProfile | undefined {
210
- const profiles: TabbyProfile[] = this.config.store.profiles ?? []
211
- return profiles.find((p) => p.id === profileId && p.type === 'local')
216
+ const isLocalType = (type: string) => type === 'local' || type?.startsWith('local:')
217
+
218
+ // First: check user profiles in config
219
+ const userProfiles: TabbyProfile[] = this.config.store?.profiles ?? []
220
+ const found = userProfiles.find((p) => p.id === profileId && isLocalType(p.type))
221
+ if (found) return found
222
+
223
+ // Fallback: check cached profiles (includes built-ins)
224
+ return this.cachedProfiles.find((p) => p.id === profileId && isLocalType(p.type))
225
+ }
226
+
227
+ collectStartupCommands(workspace: Workspace): PendingCommand[] {
228
+ const commands: PendingCommand[] = []
229
+ this.collectCommandsFromNode(workspace.root, commands)
230
+ return commands
212
231
  }
213
232
 
214
- getProfileName(profileId: string): string | undefined {
215
- return this.getProfileById(profileId)?.name
233
+ private collectCommandsFromNode(
234
+ node: WorkspacePane | WorkspaceSplit,
235
+ commands: PendingCommand[]
236
+ ): void {
237
+ if (isWorkspaceSplit(node)) {
238
+ for (const child of node.children) {
239
+ this.collectCommandsFromNode(child, commands)
240
+ }
241
+ } else if (node.startupCommand) {
242
+ commands.push({
243
+ paneId: node.id,
244
+ command: node.startupCommand,
245
+ originalTitle: node.title || '',
246
+ })
247
+ }
216
248
  }
217
249
 
218
250
  private async saveConfig(): Promise<boolean> {
package/test_cdp.py ADDED
@@ -0,0 +1,50 @@
1
+ """Test CDP connection to Tabby - click on TabbySpaces settings"""
2
+ import pychrome
3
+ import time
4
+
5
+ # Connect to Tabby
6
+ browser = pychrome.Browser(url="http://localhost:9222")
7
+ tabs = browser.list_tab()
8
+ print(f"Found {len(tabs)} tabs")
9
+
10
+ # Get the first tab (main Tabby window)
11
+ tab = tabs[0]
12
+ print(f"Using first tab")
13
+ tab.start()
14
+
15
+ # Click on TabbySpaces link
16
+ print("\nClicking on 'TabbySpaces' link...")
17
+ result = tab.Runtime.evaluate(expression="""
18
+ const link = Array.from(document.querySelectorAll('a.nav-link'))
19
+ .find(a => a.innerText.trim() === 'TabbySpaces');
20
+ if (link) {
21
+ link.click();
22
+ 'Clicked TabbySpaces!';
23
+ } else {
24
+ 'TabbySpaces link not found';
25
+ }
26
+ """)
27
+ print(f"Result: {result.get('result', {}).get('value')}")
28
+
29
+ time.sleep(0.3)
30
+
31
+ # Verify we're on TabbySpaces page - look for workspace elements
32
+ result = tab.Runtime.evaluate(expression="""
33
+ // Check for TabbySpaces specific content
34
+ const content = document.body.innerText;
35
+ const hasWorkspaces = content.includes('Workspace') || content.includes('workspace');
36
+ const activeLink = document.querySelector('a.nav-link.active');
37
+ ({
38
+ activePage: activeLink ? activeLink.innerText.trim() : 'unknown',
39
+ hasWorkspaceContent: hasWorkspaces,
40
+ pageSnippet: content.substring(0, 500)
41
+ });
42
+ """, returnByValue=True)
43
+
44
+ data = result.get('result', {}).get('value', {})
45
+ print(f"\nActive page: {data.get('activePage')}")
46
+ print(f"Has workspace content: {data.get('hasWorkspaceContent')}")
47
+ print(f"\nPage snippet:\n{data.get('pageSnippet', '')[:300]}...")
48
+
49
+ tab.stop()
50
+ print("\nDone!")
package/RELEASE_PLAN.md DELETED
@@ -1,161 +0,0 @@
1
- # TabbySpaces - Public Release Plan
2
-
3
- ## Cilj
4
-
5
- Napraviti public repo koji omogućava:
6
- 1. Korisnicima da instaliraju plugin iz Tabby Plugin Manager-a
7
- 2. Developerima da doprinose (fork → develop → PR)
8
- 3. Maintaineru (Igor) da testira i dev i production verziju istovremeno
9
-
10
- ---
11
-
12
- ## 1. Package.json izmene
13
-
14
- - [x] Verzija: `1.0.0` → `0.0.1`
15
- - [x] Dodaj `repository` polje
16
- - [x] Dodaj `homepage` polje
17
- - [x] Dodaj `bugs` polje
18
- - [x] Sredi `scripts` sekciju - uklonjena TABBY_PLUGINS zavisnost
19
-
20
- ---
21
-
22
- ## 2. Dev Workflow
23
-
24
- **Pristup:** build:dev + instalacija u plugins folder
25
-
26
- **Radi:**
27
- - [x] `npm run build:dev` - kreira dist-dev/ sa izolovanim package-om
28
- - [x] Instalacija u plugins folder
29
-
30
- **Poznati problemi:**
31
- - [x] ~~Prod i dev plugin imaju isto ime u Tabby UI~~ (REŠENO)
32
-
33
- **TODO:**
34
- - [x] ~~Popraviti izolaciju imena u UI~~ (DONE - toolbar, settings, profile groups)
35
- - [ ] Testirati watch mode
36
- - [ ] Dokumentovati u README
37
-
38
- ---
39
-
40
- ## 3. Testiranje
41
-
42
- **Koegzistencija (maintainer)** ✅ RADI
43
- ```
44
- tabby-tabbyspaces (prod) + tabby-tabbyspaces-dev (dev) u plugins folderu
45
- Različiti config keys, različita imena u UI
46
- ```
47
-
48
- **Napomene:**
49
- - Contributor workflow će biti dokumentovan u CONTRIBUTING.md
50
- - Production test će biti live npm publish
51
-
52
- ---
53
-
54
- ## 4. Potrebni fajlovi
55
-
56
- | Fajl | Status | Prioritet |
57
- |------|--------|-----------|
58
- | `README.md` | DONE | P0 |
59
- | `LICENSE` | DONE | P0 |
60
- | `CHANGELOG.md` | DONE | P0 |
61
- | `CONTRIBUTING.md` | DONE | P0 |
62
- | `.github/ISSUE_TEMPLATE/*` | DONE | P0 |
63
- | `CLAUDE.md` | DONE (EN) | P0 |
64
- | `screenshots/` | DONE | P0 |
65
-
66
- ---
67
-
68
- ## 5. Distribucija
69
-
70
- **Metod:** npm publish (jedini način da se pojavi u Tabby Plugin Manager-u)
71
-
72
- **Proces:**
73
- ```bash
74
- # Setup (jednom)
75
- npm login
76
-
77
- # Release
78
- npm version patch # ili minor/major
79
- npm publish
80
- ```
81
-
82
- **Pre-publish checklist:**
83
- - [ ] Verzija bump-ovana
84
- - [ ] Build radi (`npm run build`)
85
- - [ ] README ažuriran
86
- - [ ] CHANGELOG ažuriran
87
-
88
- ---
89
-
90
- ## 6. GitHub Repo Setup
91
-
92
- - [ ] Kreiraj repo: `github.com/halilc4/tabbyspaces`
93
- - [ ] Dodaj opis
94
- - [ ] Dodaj topics: `tabby`, `tabby-plugin`, `terminal`, `workspace`
95
- - [ ] Uključi Issues
96
- - [ ] Push koda
97
-
98
- ---
99
-
100
- ## 7. Redosled akcija
101
-
102
- ```
103
- 1. [x] Reši dev workflow → build:dev pristup
104
- 2. [x] Popravi dev vs prod izolaciju (ime u UI)
105
- 3. [x] Testiraj koegzistenciju (maintainer scenario)
106
- 4. [x] Napravi LICENSE (MIT, engleski)
107
- 5. [x] Napravi README.md (engleski, Claude Code attribution, screenshots)
108
- 6. [x] Napravi CHANGELOG.md
109
- 7. [x] Napravi CONTRIBUTING.md
110
- 8. [x] Napravi .github/ISSUE_TEMPLATE/*
111
- 9. [x] Prevedi CLAUDE.md na engleski
112
- 10. [ ] Kreiraj GitHub repo (halilc4/tabbyspaces)
113
- 11. [ ] Push
114
- 12. [ ] npm publish
115
- 13. [ ] Testiraj instalaciju iz Tabby Plugin Manager-a
116
- ```
117
-
118
- ---
119
-
120
- ## Odluke
121
-
122
- 1. **GitHub username** - `halilc4` → `github.com/halilc4/tabbyspaces`
123
- 2. **CHANGELOG** - DA, vodimo ga
124
- 3. **Jezici** - README i LICENSE na engleskom
125
- 4. **Attribution** - README mora jasno naglasiti:
126
- - Kod 100% napisan od Claude Code-a
127
- - Igor = ideja i product vision
128
- - Igor toliko mrzi Angular da nije ni pogledao kod
129
-
130
- ---
131
-
132
- ## Otvorena pitanja
133
-
134
- ~~1. **CLAUDE.md jezik** - Prevesti na engleski za contributore?~~ → DA, engleski
135
-
136
- ---
137
-
138
- ## 8. Final Review (2026-01-03)
139
-
140
- **Status:** ✅ PASS
141
-
142
- **Pregledano:**
143
- - [x] Struktura projekta - čista, standardni Angular pattern
144
- - [x] Kod - tipiziran, bez `any`, čitljiv
145
- - [x] README.md - jasan, sa screenshotovima
146
- - [x] LICENSE, CONTRIBUTING.md, CHANGELOG.md - kompletno
147
- - [x] package.json metapodaci - repo, bugs, homepage
148
- - [x] .github/ISSUE_TEMPLATE/* - bug report, feature request
149
- - [x] Dev/Prod izolacija - radi
150
-
151
- **Zaključak:** Projekat je spreman za public release.
152
-
153
- **Sledeća sesija:** Full publish (GitHub repo + npm publish)
154
-
155
- ---
156
-
157
- ## Stil dokumentacije
158
-
159
- - Bez mainstream GitHub corporate stila
160
- - Bez AI slop-a (generic, over-polished)
161
- - Direktno, iskreno, sa karakterom
Binary file