@spencer-kit/coder-studio 0.4.5 → 0.4.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [#64](https://github.com/spencerkit/coder-studio/pull/64) [`d14fe08`](https://github.com/spencerkit/coder-studio/commit/d14fe08d7861652a9290559d5a59aa766c286309) Thanks [@pallyoung](https://github.com/pallyoung)! - Refine workspace theming and session ergonomics by adding pane drag reordering,
8
+ stabilizing update checks, hardening PTY color environment isolation, and
9
+ polishing shared desktop surfaces across the app.
10
+
11
+ ## 0.4.6
12
+
13
+ ### Patch Changes
14
+
15
+ - [#62](https://github.com/spencerkit/coder-studio/pull/62) [`535c3c0`](https://github.com/spencerkit/coder-studio/commit/535c3c09cc5895e3b3b949067633f8a6bb3644f8) Thanks [@pallyoung](https://github.com/pallyoung)! - Improve desktop workspace ergonomics by adding keyboard pane navigation,
16
+ supporting workspace path drops into terminal sessions, and launching themed
17
+ PTYs with terminal-aware background environment hints.
18
+
3
19
  ## 0.4.5
4
20
 
5
21
  ### Patch Changes
package/dist/esm/bin.mjs CHANGED
@@ -6892,7 +6892,8 @@ var init_manager3 = __esm({
6892
6892
  ...cmd.env,
6893
6893
  CODER_STUDIO_SESSION_ID: sessionId
6894
6894
  },
6895
- title: req.provider.displayName
6895
+ title: req.provider.displayName,
6896
+ themeBackground: req.themeBackground
6896
6897
  };
6897
6898
  const terminal = this.deps.terminalMgr.create(terminalSpec);
6898
6899
  const active = new ActiveSession({
@@ -12152,6 +12153,39 @@ var init_types2 = __esm({
12152
12153
  function isTerminalTraceEnabled() {
12153
12154
  return process.env.CODER_STUDIO_TERMINAL_TRACE === "1";
12154
12155
  }
12156
+ function parseHexColor(input2) {
12157
+ const trimmed = input2.trim();
12158
+ if (!trimmed.startsWith("#")) {
12159
+ return null;
12160
+ }
12161
+ const hex = trimmed.slice(1);
12162
+ let r;
12163
+ let g;
12164
+ let b;
12165
+ if (hex.length === 3) {
12166
+ r = Number.parseInt(hex[0] + hex[0], 16);
12167
+ g = Number.parseInt(hex[1] + hex[1], 16);
12168
+ b = Number.parseInt(hex[2] + hex[2], 16);
12169
+ } else if (hex.length === 6 || hex.length === 8) {
12170
+ r = Number.parseInt(hex.slice(0, 2), 16);
12171
+ g = Number.parseInt(hex.slice(2, 4), 16);
12172
+ b = Number.parseInt(hex.slice(4, 6), 16);
12173
+ } else {
12174
+ return null;
12175
+ }
12176
+ if ([r, g, b].some((v) => Number.isNaN(v))) {
12177
+ return null;
12178
+ }
12179
+ return { r, g, b };
12180
+ }
12181
+ function computeColorFgBg(background) {
12182
+ const rgb = parseHexColor(background);
12183
+ if (!rgb) {
12184
+ return void 0;
12185
+ }
12186
+ const luma = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
12187
+ return luma > 0.5 ? "0;15" : "15;0";
12188
+ }
12155
12189
  function countOccurrences(text, needle) {
12156
12190
  return text.split(needle).length - 1;
12157
12191
  }
@@ -12208,6 +12242,7 @@ var init_manager5 = __esm({
12208
12242
  */
12209
12243
  create(spec) {
12210
12244
  const id = generateId();
12245
+ const derivedColorFgBg = spec.themeBackground ? computeColorFgBg(spec.themeBackground) : void 0;
12211
12246
  const terminalEnv = {
12212
12247
  ...Object.fromEntries(
12213
12248
  Object.entries(process.env).filter((e) => e[1] != null)
@@ -12215,8 +12250,16 @@ var init_manager5 = __esm({
12215
12250
  TERM: "xterm-256color",
12216
12251
  COLORTERM: "truecolor",
12217
12252
  FORCE_COLOR: "3",
12253
+ ...derivedColorFgBg ? { COLORFGBG: derivedColorFgBg } : {},
12218
12254
  ...spec.env
12219
12255
  };
12256
+ delete terminalEnv.COLORFGBG;
12257
+ if (derivedColorFgBg) {
12258
+ terminalEnv.COLORFGBG = derivedColorFgBg;
12259
+ }
12260
+ if (spec.env?.COLORFGBG) {
12261
+ terminalEnv.COLORFGBG = spec.env.COLORFGBG;
12262
+ }
12220
12263
  let pty;
12221
12264
  try {
12222
12265
  pty = this.deps.ptyHost.spawn(spec.argv, {
@@ -12616,7 +12659,7 @@ var init_update_service = __esm({
12616
12659
  "packages/server/src/update/update-service.ts"() {
12617
12660
  "use strict";
12618
12661
  init_src3();
12619
- UpdateService = class {
12662
+ UpdateService = class _UpdateService {
12620
12663
  constructor(deps) {
12621
12664
  this.deps = deps;
12622
12665
  this.now = deps.now ?? Date.now;
@@ -12638,12 +12681,14 @@ var init_update_service = __esm({
12638
12681
  this.spawnDetachedWorkerImpl = deps.spawnDetachedWorker;
12639
12682
  }
12640
12683
  deps;
12684
+ static CHECK_TIMEOUT_MS = 15e3;
12641
12685
  now;
12642
12686
  runtime;
12643
12687
  updateWorkerLogFilePath;
12644
12688
  runLatestVersionLookup;
12645
12689
  spawnDetachedWorkerImpl;
12646
12690
  scheduleTimer = null;
12691
+ inFlightCheck = null;
12647
12692
  start() {
12648
12693
  this.reconcileOnStartup();
12649
12694
  this.reloadScheduleFromSettings();
@@ -12674,10 +12719,8 @@ var init_update_service = __esm({
12674
12719
  this.scheduleTimer.unref?.();
12675
12720
  }
12676
12721
  getStateView() {
12677
- return {
12678
- ...this.deps.updateStateRepo.get(),
12679
- ...this.getSupportInfo()
12680
- };
12722
+ const persisted = this.deps.updateStateRepo.get();
12723
+ return this.composeStateView(persisted);
12681
12724
  }
12682
12725
  getPrepareInstallState() {
12683
12726
  const state = this.getStateView();
@@ -12694,36 +12737,15 @@ var init_update_service = __esm({
12694
12737
  if (current.updateStatus === "installing" || current.updateStatus === "restarting") {
12695
12738
  throw createBusyError("Update installation is already in progress");
12696
12739
  }
12697
- if (current.updateStatus === "checking") {
12740
+ if (this.inFlightCheck) {
12698
12741
  throw createBusyError("Update check is already in progress");
12699
12742
  }
12700
- this.persistAndBroadcast({
12701
- updateStatus: "checking",
12702
- finishedAt: null,
12703
- errorSummary: null
12704
- });
12743
+ this.inFlightCheck = this.runCheckForUpdates();
12744
+ this.broadcastStateChange();
12705
12745
  try {
12706
- const latestVersion = await this.runLatestVersionLookup(this.runtime.packageName);
12707
- const availability = compareVersions(latestVersion, this.runtime.currentVersion) > 0 ? "update_available" : "up_to_date";
12708
- return this.persistAndBroadcast({
12709
- currentVersion: this.runtime.currentVersion,
12710
- latestVersion,
12711
- availability,
12712
- updateStatus: "idle",
12713
- lastCheckedAt: this.now(),
12714
- errorSummary: null,
12715
- requiresManualStep: false,
12716
- manualCommand: null
12717
- });
12718
- } catch (error) {
12719
- const message = error instanceof Error ? error.message : String(error);
12720
- return this.persistAndBroadcast({
12721
- currentVersion: this.runtime.currentVersion,
12722
- availability: "check_failed",
12723
- updateStatus: "idle",
12724
- lastCheckedAt: this.now(),
12725
- errorSummary: message
12726
- });
12746
+ return await this.inFlightCheck;
12747
+ } finally {
12748
+ this.inFlightCheck = null;
12727
12749
  }
12728
12750
  }
12729
12751
  prepareInstall() {
@@ -12738,6 +12760,9 @@ var init_update_service = __esm({
12738
12760
  if (state.updateStatus === "installing" || state.updateStatus === "restarting") {
12739
12761
  throw createBusyError("Update installation is already in progress");
12740
12762
  }
12763
+ if (this.inFlightCheck) {
12764
+ throw createBusyError("Update check is already in progress");
12765
+ }
12741
12766
  const targetVersion = input2.targetVersion ?? state.latestVersion;
12742
12767
  if (!targetVersion) {
12743
12768
  throw createValidationError("update_no_target", "No target version is available");
@@ -12812,6 +12837,15 @@ var init_update_service = __esm({
12812
12837
  errorSummary: null
12813
12838
  });
12814
12839
  }
12840
+ if (current.updateStatus === "checking") {
12841
+ return this.persistAndBroadcast({
12842
+ currentVersion: this.runtime.currentVersion,
12843
+ availability: "check_failed",
12844
+ updateStatus: "failed",
12845
+ finishedAt: this.now(),
12846
+ errorSummary: "Update check did not complete before the service restarted"
12847
+ });
12848
+ }
12815
12849
  if (current.updateStatus === "installing" || current.updateStatus === "restarting") {
12816
12850
  return this.persistAndBroadcast({
12817
12851
  currentVersion: this.runtime.currentVersion,
@@ -12862,18 +12896,6 @@ var init_update_service = __esm({
12862
12896
  hasActiveWork: runningTerminalCount > 0 || runningSessionCount > 0 || runningSupervisorCount > 0
12863
12897
  };
12864
12898
  }
12865
- persistAndBroadcast(patch) {
12866
- const snapshot = this.deps.updateStateRepo.update((current) => ({
12867
- ...patch,
12868
- currentVersion: patch.currentVersion ?? current.currentVersion
12869
- }));
12870
- const view = {
12871
- ...snapshot,
12872
- ...this.getSupportInfo()
12873
- };
12874
- this.deps.broadcaster.broadcast("update.state.changed", view);
12875
- return view;
12876
- }
12877
12899
  buildManualCommand(targetVersion) {
12878
12900
  return [
12879
12901
  `${this.runtime.npmCommand ?? "npm"} install -g ${this.runtime.packageName}@${targetVersion}`,
@@ -12903,6 +12925,83 @@ var init_update_service = __esm({
12903
12925
  });
12904
12926
  child.unref();
12905
12927
  }
12928
+ composeStateView(snapshot, options) {
12929
+ if (options?.includeInFlightCheck !== false && this.inFlightCheck) {
12930
+ return {
12931
+ ...snapshot,
12932
+ ...this.getSupportInfo(),
12933
+ updateStatus: "checking",
12934
+ errorSummary: null
12935
+ };
12936
+ }
12937
+ return {
12938
+ ...snapshot,
12939
+ ...this.getSupportInfo()
12940
+ };
12941
+ }
12942
+ broadcastStateChange() {
12943
+ this.deps.broadcaster.broadcast("update.state.changed", this.getStateView());
12944
+ }
12945
+ async runCheckForUpdates() {
12946
+ try {
12947
+ const latestVersion = await this.withCheckTimeout(
12948
+ this.runLatestVersionLookup(this.runtime.packageName)
12949
+ );
12950
+ const availability = compareVersions(latestVersion, this.runtime.currentVersion) > 0 ? "update_available" : "up_to_date";
12951
+ return this.persistAndBroadcast(
12952
+ {
12953
+ currentVersion: this.runtime.currentVersion,
12954
+ latestVersion,
12955
+ availability,
12956
+ updateStatus: "idle",
12957
+ lastCheckedAt: this.now(),
12958
+ errorSummary: null,
12959
+ requiresManualStep: false,
12960
+ manualCommand: null
12961
+ },
12962
+ false
12963
+ );
12964
+ } catch (error) {
12965
+ const message = error instanceof Error ? error.message : String(error);
12966
+ return this.persistAndBroadcast(
12967
+ {
12968
+ currentVersion: this.runtime.currentVersion,
12969
+ availability: "check_failed",
12970
+ updateStatus: "idle",
12971
+ lastCheckedAt: this.now(),
12972
+ errorSummary: message
12973
+ },
12974
+ false
12975
+ );
12976
+ }
12977
+ }
12978
+ async withCheckTimeout(promise) {
12979
+ let timeoutHandle = null;
12980
+ try {
12981
+ return await Promise.race([
12982
+ promise,
12983
+ new Promise((_, reject) => {
12984
+ timeoutHandle = setTimeout(() => {
12985
+ reject(new Error(`Update check timed out after ${_UpdateService.CHECK_TIMEOUT_MS}ms`));
12986
+ }, _UpdateService.CHECK_TIMEOUT_MS);
12987
+ timeoutHandle.unref?.();
12988
+ })
12989
+ ]);
12990
+ } finally {
12991
+ if (timeoutHandle) {
12992
+ clearTimeout(timeoutHandle);
12993
+ }
12994
+ }
12995
+ }
12996
+ persistAndBroadcast(patch, includeInFlightCheck = true) {
12997
+ const snapshot = this.deps.updateStateRepo.update((current) => ({
12998
+ ...patch,
12999
+ currentVersion: patch.currentVersion ?? current.currentVersion
13000
+ }));
13001
+ const view = this.composeStateView(snapshot, { includeInFlightCheck });
13002
+ this.deps.broadcaster.broadcast("update.state.changed", view);
13003
+ return view;
13004
+ }
12906
13005
  };
12907
13006
  }
12908
13007
  });
@@ -13759,7 +13858,8 @@ var init_terminal = __esm({
13759
13858
  workspaceId: z6.string(),
13760
13859
  cols: z6.number().int().positive().optional(),
13761
13860
  rows: z6.number().int().positive().optional(),
13762
- cwdPath: z6.string().optional()
13861
+ cwdPath: z6.string().optional(),
13862
+ themeBackground: z6.string().regex(/^#[0-9a-fA-F]{3,8}$/).optional()
13763
13863
  }),
13764
13864
  async (args, ctx) => {
13765
13865
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -13797,7 +13897,8 @@ var init_terminal = __esm({
13797
13897
  title: shell.title,
13798
13898
  cwd,
13799
13899
  cols: args.cols ?? 120,
13800
- rows: args.rows ?? 30
13900
+ rows: args.rows ?? 30,
13901
+ themeBackground: args.themeBackground
13801
13902
  });
13802
13903
  return terminal;
13803
13904
  }
@@ -15369,7 +15470,8 @@ var init_session2 = __esm({
15369
15470
  z12.object({
15370
15471
  workspaceId: z12.string(),
15371
15472
  providerId: z12.string(),
15372
- draft: z12.string().optional()
15473
+ draft: z12.string().optional(),
15474
+ themeBackground: z12.string().regex(/^#[0-9a-fA-F]{3,8}$/).optional()
15373
15475
  }),
15374
15476
  async (args, ctx) => {
15375
15477
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -15397,7 +15499,8 @@ var init_session2 = __esm({
15397
15499
  workspacePath: workspace.path,
15398
15500
  providerId: args.providerId,
15399
15501
  provider,
15400
- draft: args.draft
15502
+ draft: args.draft,
15503
+ themeBackground: args.themeBackground
15401
15504
  });
15402
15505
  }
15403
15506
  );