pi-minimalist 0.1.2 → 0.2.2

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.
@@ -3,12 +3,9 @@
3
3
  *
4
4
  * WHY settings.json AND NOT OUR OWN FILE
5
5
  * --------------------------------------
6
- * The old `pi-minimalist.json` was a second config file the user had to know
7
- * about, and it hardcoded `~/.pi/agent`, so anyone setting
8
- * `PI_CODING_AGENT_DIR` silently wrote their preference somewhere it would never
9
- * be read back. settings.json is where a Pi user already looks, it is what
10
- * `pi-env` reads, and Pi tolerates unknown top-level keys (the published
11
- * `pi-powerline-footer` extension does exactly this with its `powerline` key).
6
+ * It is where a Pi user already looks, it is what `pi-env` reads, and Pi
7
+ * tolerates unknown top-level keys (the published `pi-powerline-footer`
8
+ * extension does exactly this with its `powerline` key).
12
9
  *
13
10
  * SCOPE
14
11
  * defaults < global agent settings.json (PI_CODING_AGENT_DIR if set).
@@ -29,6 +26,11 @@ import {
29
26
  DEFAULTS,
30
27
  DEFAULT_TOKENS,
31
28
  GLYPH_PRESETS,
29
+ FRESH_PRESET,
30
+ LOOK_KEYS,
31
+ PRESETS,
32
+ isFresh,
33
+ isPreset,
32
34
  SETTINGS_KEY,
33
35
  type BasicSettings,
34
36
  type Glyphs,
@@ -52,11 +54,6 @@ export function globalSettingsPath(env = process.env): string {
52
54
  return join(agentDir(env), "settings.json");
53
55
  }
54
56
 
55
- /** Legacy state file written by the removed `/quiet` command. */
56
- export function legacyQuietPath(env = process.env): string {
57
- return join(agentDir(env), "pi-minimalist.json");
58
- }
59
-
60
57
  type Json = Record<string, unknown>;
61
58
 
62
59
  function isRecord(value: unknown): value is Json {
@@ -70,59 +67,12 @@ function isRecord(value: unknown): value is Json {
70
67
  /**
71
68
  * Remove `//` and block comments that are outside string literals.
72
69
  *
73
- * Pi bundles `strip-json-comments` but does not re-export it to extensions, so
74
- * this is a small character scanner. The only real subtlety is that `//` inside a
75
- * JSON string (a URL, a Windows path) must NOT start a comment.
70
+ * Pi bundles `strip-json-comments` but does not re-export it to extensions. The
71
+ * regex matches a whole string first (escapes included) and keeps it, so `//`
72
+ * inside a URL or path never starts a comment.
76
73
  */
77
74
  export function stripJsonComments(text: string): string {
78
- let out = "";
79
- let inString = false;
80
- let escaped = false;
81
- let comment: "line" | "block" | undefined;
82
-
83
- for (let i = 0; i < text.length; i++) {
84
- const char = text[i];
85
- const next = text[i + 1];
86
-
87
- if (comment === "line") {
88
- if (char === "\n") {
89
- comment = undefined;
90
- out += char;
91
- }
92
- continue;
93
- }
94
- if (comment === "block") {
95
- if (char === "*" && next === "/") {
96
- comment = undefined;
97
- i++;
98
- }
99
- continue;
100
- }
101
- if (inString) {
102
- out += char;
103
- if (escaped) escaped = false;
104
- else if (char === "\\") escaped = true;
105
- else if (char === '"') inString = false;
106
- continue;
107
- }
108
- if (char === '"') {
109
- inString = true;
110
- out += char;
111
- continue;
112
- }
113
- if (char === "/" && next === "/") {
114
- comment = "line";
115
- i++;
116
- continue;
117
- }
118
- if (char === "/" && next === "*") {
119
- comment = "block";
120
- i++;
121
- continue;
122
- }
123
- out += char;
124
- }
125
- return out;
75
+ return text.replace(/("(?:\\.|[^"\\])*")|\/\/[^\n]*|\/\*[\s\S]*?\*\//g, (_, string) => string ?? "");
126
76
  }
127
77
 
128
78
  /** True when a file carries comments, so writing it back would destroy them. */
@@ -153,8 +103,10 @@ function readSettingsFile(path: string): Json {
153
103
  * this is hand-edited input.
154
104
  */
155
105
  function coerce(raw: unknown, into: Settings): Settings {
156
- if (!isRecord(raw)) return into;
157
106
  const out = { ...into };
107
+ // A user with no basic key has never configured anything: they get the default preset.
108
+ if (isFresh(raw)) out.preset = FRESH_PRESET;
109
+ if (!isRecord(raw)) return out;
158
110
 
159
111
  for (const key of BASIC_KEYS) {
160
112
  if (typeof DEFAULT_BASIC[key] === "boolean" && typeof raw[key] === "boolean") {
@@ -169,6 +121,7 @@ function coerce(raw: unknown, into: Settings): Settings {
169
121
  if (Array.isArray(raw.excludeTools)) {
170
122
  out.excludeTools = raw.excludeTools.filter((name): name is string => typeof name === "string");
171
123
  }
124
+ if (isPreset(raw.preset)) out.preset = raw.preset;
172
125
  if (raw.glyphStyle === "unicode" || raw.glyphStyle === "ascii") {
173
126
  out.glyphStyle = raw.glyphStyle;
174
127
  }
@@ -183,9 +136,6 @@ function coerce(raw: unknown, into: Settings): Settings {
183
136
  // therefore the honest boundary of what this function can check.
184
137
  out.tokens = pickStrings(raw.tokens, Object.keys(DEFAULT_TOKENS) as (keyof Tokens)[]) as Partial<Tokens>;
185
138
  }
186
- if (typeof raw.maxDetailChars === "number" && raw.maxDetailChars > 0) {
187
- out.maxDetailChars = Math.floor(raw.maxDetailChars);
188
- }
189
139
  return out;
190
140
  }
191
141
 
@@ -205,21 +155,14 @@ export function loadSettings(env = process.env): Settings {
205
155
  }
206
156
 
207
157
  /**
208
- * One-time migration of the old `/quiet` preference.
209
- *
210
- * Returns a value only when settings.json has no `groupToolRuns` yet, so an
211
- * explicit new setting always wins and a stale legacy file cannot resurrect a
212
- * preference the user has since changed.
158
+ * Look keys to write when the user switches to `custom` for the first time: the
159
+ * `lite` look, so "custom" starts from something known. Undefined when the file
160
+ * already holds any of the user's own look keys (they are kept untouched).
213
161
  */
214
- export function migratedQuiet(env = process.env): boolean | undefined {
215
- const existing = readSettingsFile(globalSettingsPath(env))[SETTINGS_KEY];
216
- if (isRecord(existing) && typeof existing.groupToolRuns === "boolean") return undefined;
217
- try {
218
- const legacy = JSON.parse(readFileSync(legacyQuietPath(env), "utf8"));
219
- return isRecord(legacy) && legacy.quiet === true ? true : undefined;
220
- } catch {
221
- return undefined;
222
- }
162
+ export function customSeed(env = process.env): Record<string, boolean> | undefined {
163
+ const block = readSettingsFile(globalSettingsPath(env))[SETTINGS_KEY];
164
+ const own = isRecord(block) && LOOK_KEYS.some((key) => key in block);
165
+ return own ? undefined : PRESETS.lite;
223
166
  }
224
167
 
225
168
  // ---------------------------------------------------------------------------
@@ -232,7 +175,7 @@ export type SaveResult =
232
175
 
233
176
  /**
234
177
  * Persist only the supplied editor-managed settings into the global agent file.
235
- * A normal edit passes one key; Restore defaults intentionally passes all.
178
+ * A normal edit passes one key.
236
179
  * Hand-written `glyphs`/`tokens`/`excludeTools` values survive untouched.
237
180
  *
238
181
  * REFUSES to write a file containing comments. `JSON.stringify` would silently
@@ -263,6 +206,8 @@ export function saveBasicSettings(settings: Partial<BasicSettings>, env = proces
263
206
  }
264
207
  const existing = isRecord(root[SETTINGS_KEY]) ? root[SETTINGS_KEY] : {};
265
208
  const merged: Json = { ...existing };
209
+ // The first write must pin the fresh-install preset, or the file would stop looking fresh and become `custom`.
210
+ if (isFresh(existing)) merged.preset = FRESH_PRESET;
266
211
  for (const key of BASIC_KEYS) {
267
212
  if (settings[key] !== undefined) merged[key] = settings[key];
268
213
  }
package/src/config-ui.ts CHANGED
@@ -3,7 +3,9 @@
3
3
  import { truncateToWidth, type AutocompleteItem, type Component, type SettingItem, type SettingsList, type SettingsListTheme } from "@earendil-works/pi-tui";
4
4
  import {
5
5
  BASIC_KEYS,
6
- DEFAULT_BASIC,
6
+ LOOK_KEYS,
7
+ PRESET_NAMES,
8
+ isPreset,
7
9
  type BasicKey,
8
10
  type BasicSettings,
9
11
  type Config,
@@ -20,6 +22,11 @@ type Field = {
20
22
 
21
23
  /** Label and hover description for every basic setting, in display order. */
22
24
  export const FIELDS: Field[] = [
25
+ {
26
+ key: "preset",
27
+ label: "Preset",
28
+ description: "off = native Pi, lite = one-line rows, full = + groups and thinking rows, max = + collapsed earlier activity. custom uses your own values. A preset only sets the four look rows; your other settings are kept.",
29
+ },
23
30
  {
24
31
  key: "compactToolRows",
25
32
  label: "Compact tool rows",
@@ -35,12 +42,6 @@ export const FIELDS: Field[] = [
35
42
  label: "Collapse earlier activity",
36
43
  description: "Keep the latest assistant reply visible; replace earlier text, thinking, and tool calls with one summary.",
37
44
  },
38
- {
39
- key: "foldActivityOnFinalAnswer",
40
- label: " Collapse when final answer starts",
41
- description: "Keep work visible until OpenAI starts its final answer, then collapse the earlier activity.",
42
- activityOption: true,
43
- },
44
45
  {
45
46
  key: "activitySummary",
46
47
  label: " Activity summary",
@@ -83,18 +84,27 @@ type InputComponent = Component & { handleInput(data: string): void };
83
84
  type SettingsListConstructor = new (...args: ConstructorParameters<typeof SettingsList>) => SettingsList;
84
85
  type BasicValue = BasicSettings[BasicKey];
85
86
 
86
- /** Conditional rows plus their display values. */
87
- export function items(settings: Settings): SettingItem[] {
87
+ const CHOICES: Partial<Record<BasicKey, string[]>> = {
88
+ preset: [...PRESET_NAMES],
89
+ activitySummary: ["elapsed time", "tools used"],
90
+ glyphStyle: ["Unicode", "ASCII"],
91
+ };
92
+
93
+ const isLook = (key: string) => (LOOK_KEYS as readonly string[]).includes(key);
94
+
95
+ /** Conditional rows plus their display values. `dim` greys the look rows a preset owns. */
96
+ export function items(settings: Settings, dim?: (text: string) => string): SettingItem[] {
88
97
  return FIELDS.filter((field) => !field.activityOption || settings.foldIntermediateActivity).map(
89
98
  ({ key, label, description }) => {
90
- const value = settings[key];
91
- return {
92
- id: key,
93
- label,
94
- description,
95
- currentValue: displayValue(value),
96
- values: key === "activitySummary" ? ["elapsed time", "tools used"] : key === "glyphStyle" ? ["Unicode", "ASCII"] : ["on", "off"],
97
- };
99
+ const value = displayValue(settings[key]);
100
+ // SettingsList has no disabled rows, so a locked row shows its dimmed value
101
+ // as its ONLY value: Enter "cycles" to the same text and nothing changes.
102
+ if (dim && isLook(key)) {
103
+ const shown = dim(value);
104
+ const hint = `Set by the '${settings.preset}' preset. Switch Preset to 'custom' to edit (your custom values are kept).`;
105
+ return { id: key, label, description: hint, currentValue: shown, values: [shown] };
106
+ }
107
+ return { id: key, label, description, currentValue: value, values: CHOICES[key] ?? ["on", "off"] };
98
108
  },
99
109
  );
100
110
  }
@@ -104,39 +114,35 @@ export type ConfigScreenDeps = {
104
114
  theme: SettingsListTheme;
105
115
  config: Config;
106
116
  onChange: (key: BasicKey, value: BasicValue) => void;
107
- onReset: () => void;
108
117
  onClose: () => void;
109
118
  };
110
119
 
111
120
  /** Build a live screen; toggling prose folding immediately adds/removes its two child rows. */
112
121
  export function createConfigScreen(deps: ConfigScreenDeps): InputComponent {
113
- const { SettingsList, theme, config, onChange, onReset, onClose } = deps;
122
+ const { SettingsList, theme, config, onChange, onClose } = deps;
114
123
  let list: SettingsList;
124
+ let locked = false;
115
125
 
116
126
  const build = () => {
117
- const rows = [
118
- ...items(config.all()),
119
- {
120
- id: "restoreDefaults",
121
- label: "Restore defaults",
122
- description: "Reset the settings in this editor, including Symbols. Custom glyphs, colours, and excluded tools are kept.",
123
- currentValue: "Enter to restore",
124
- values: ["Restore"],
125
- },
126
- ];
127
+ const preset = config.all().preset;
128
+ locked = preset !== "custom";
129
+ const rows = items(config.all(), locked ? theme.hint : undefined);
127
130
  return new SettingsList(
128
131
  rows,
129
132
  rows.length,
130
133
  theme,
131
134
  (id, displayValue) => {
132
- if (id === "restoreDefaults") {
133
- for (const key of BASIC_KEYS) config.set(key, DEFAULT_BASIC[key]);
134
- onReset();
135
+ if (!isBasicKey(id)) return;
136
+ if (id === "preset") {
137
+ const value = isPreset(displayValue) ? displayValue : "custom";
138
+ config.set(id, value);
139
+ onChange(id, value);
135
140
  list = build();
136
141
  list.selectItem(id);
137
142
  return;
138
143
  }
139
- if (!isBasicKey(id)) return;
144
+ // Locked rows only echo their own text; do nothing (their description explains).
145
+ if (locked && isLook(id)) return;
140
146
  if (id === "activitySummary") {
141
147
  const value = displayValue === "tools used" ? "tools" : "elapsed";
142
148
  config.set(id, value);
@@ -176,6 +182,7 @@ function isBasicKey(id: string): id is BasicKey {
176
182
  }
177
183
 
178
184
  function displayValue(value: BasicValue): string {
185
+ if (isPreset(value)) return value;
179
186
  if (value === "elapsed") return "elapsed time";
180
187
  if (value === "tools") return "tools used";
181
188
  if (value === "unicode") return "Unicode";
@@ -199,7 +206,8 @@ export function summary(settings: Settings): string {
199
206
  const width = Math.max(...visible.map((field) => field.label.length));
200
207
  return [
201
208
  ...visible.map(({ key, label }) => {
202
- return ` ${label.padEnd(width)} ${displayValue(settings[key])}`;
209
+ const dimmed = settings.preset !== "custom" && isLook(key) ? " (from preset)" : "";
210
+ return ` ${label.padEnd(width)} ${displayValue(settings[key])}${dimmed}`;
203
211
  }),
204
212
  "",
205
213
  " /minimalist change these",
package/src/config.ts CHANGED
@@ -7,11 +7,7 @@
7
7
  * are readable on their own.
8
8
  */
9
9
 
10
- import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
11
-
12
- // ThemeBg is not exported publicly, so recover it from Theme.bg's own signature
13
- // rather than importing a deep dist path (which is not a package export).
14
- type ThemeBg = Parameters<Theme["bg"]>[0];
10
+ import type { ThemeColor } from "@earendil-works/pi-coding-agent";
15
11
 
16
12
  /** Top-level key inside Pi's settings.json. */
17
13
  export const SETTINGS_KEY = "minimalist";
@@ -41,6 +37,9 @@ export type Glyphs = {
41
37
 
42
38
  export type GlyphStyle = "unicode" | "ascii";
43
39
 
40
+ /** Marker on the header of an opened run. Not user-configurable. */
41
+ export const OPEN_MARKER: Record<GlyphStyle, string> = { unicode: "▾", ascii: "v" };
42
+
44
43
  export const GLYPH_PRESETS: Record<GlyphStyle, Glyphs> = {
45
44
  unicode: { done: "✓", failed: "✗", running: "•", queued: "›", gutter: "▌", timer: "⏱", count: "×" },
46
45
  // Every ASCII glyph is exactly one column wide, so rows stay aligned in fonts
@@ -56,9 +55,8 @@ export const GLYPH_PRESETS: Record<GlyphStyle, Glyphs> = {
56
55
  * Which Pi theme token paints each part of a row.
57
56
  *
58
57
  * Overridable, but the defaults are deliberate: the action word is a LABEL, not
59
- * a warning, so it uses `success` green even for mutating tools. An earlier
60
- * version used warning orange, which collided with orange meaning "highlighted
61
- * prose".
58
+ * a warning, so it uses `success` green even for mutating tools (orange would
59
+ * collide with "highlighted prose").
62
60
  */
63
61
  export type Tokens = {
64
62
  label: ThemeColor;
@@ -66,7 +64,6 @@ export type Tokens = {
66
64
  thinking: ThemeColor;
67
65
  error: ThemeColor;
68
66
  outputGutter: ThemeColor;
69
- activeBg: ThemeBg;
70
67
  };
71
68
 
72
69
  export const DEFAULT_TOKENS: Tokens = {
@@ -75,7 +72,6 @@ export const DEFAULT_TOKENS: Tokens = {
75
72
  thinking: "thinkingText",
76
73
  error: "error",
77
74
  outputGutter: "borderMuted",
78
- activeBg: "toolPendingBg",
79
75
  };
80
76
 
81
77
  // ---------------------------------------------------------------------------
@@ -85,15 +81,34 @@ export const DEFAULT_TOKENS: Tokens = {
85
81
  /** Settings `/minimalist` can edit: visible effect, no vocabulary needed. */
86
82
  export type ActivitySummary = "elapsed" | "tools";
87
83
 
84
+ export const PRESET_NAMES = ["off", "lite", "full", "max", "custom"] as const;
85
+ export type Preset = (typeof PRESET_NAMES)[number];
86
+
87
+ export function isPreset(value: unknown): value is Preset {
88
+ return (PRESET_NAMES as readonly unknown[]).includes(value);
89
+ }
90
+
91
+ /** The only settings a preset names. Every other setting stays the user's own. */
92
+ export const LOOK_KEYS = ["compactToolRows", "groupToolRuns", "foldIntermediateActivity", "thinkingAsToolCall"] as const;
93
+ export type LookKey = (typeof LOOK_KEYS)[number];
94
+
95
+ /** Preset of a user who has never configured anything (no basic key in the `minimalist` block). */
96
+ export const FRESH_PRESET: Preset = "full";
97
+
98
+ /** True when the `minimalist` block holds no basic key, so the user never configured anything. */
99
+ export function isFresh(block: unknown): boolean {
100
+ return typeof block !== "object" || block === null || !BASIC_KEYS.some((key) => key in block);
101
+ }
102
+
88
103
  export type BasicSettings = {
104
+ /** A named bundle of the four look settings below. `custom` uses the user's own values for all of them. */
105
+ preset: Preset;
89
106
  /** Master switch. Off restores Pi's native tool cards entirely. */
90
107
  compactToolRows: boolean;
91
108
  /** Fold adjacent finished rows into one summary line. */
92
109
  groupToolRuns: boolean;
93
110
  /** Replace all but the latest assistant prose block with one summary row. */
94
111
  foldIntermediateActivity: boolean;
95
- /** Keep commentary visible until OpenAI starts its final-answer message. */
96
- foldActivityOnFinalAnswer: boolean;
97
112
  /** What replaces the activity hidden before the latest prose block. */
98
113
  activitySummary: ActivitySummary;
99
114
  /** Draw the left gutter bar. */
@@ -116,21 +131,15 @@ export type AdvancedSettings = {
116
131
  excludeTools: string[];
117
132
  glyphs: Partial<Glyphs>;
118
133
  tokens: Partial<Tokens>;
119
- /**
120
- * Sanity cap on detail text length. NOT a display limit — rows truncate at the
121
- * real viewport width. This only stops a multi-megabyte heredoc from being
122
- * whitespace-collapsed, colored and measured on every repaint.
123
- */
124
- maxDetailChars: number;
125
134
  };
126
135
 
127
136
  export type Settings = BasicSettings & AdvancedSettings;
128
137
 
129
138
  export const DEFAULT_BASIC: BasicSettings = {
139
+ preset: "custom",
130
140
  compactToolRows: true,
131
141
  groupToolRuns: true,
132
142
  foldIntermediateActivity: false,
133
- foldActivityOnFinalAnswer: false,
134
143
  activitySummary: "elapsed",
135
144
  gutter: true,
136
145
  timer: true,
@@ -140,15 +149,24 @@ export const DEFAULT_BASIC: BasicSettings = {
140
149
  glyphStyle: "unicode",
141
150
  };
142
151
 
152
+ /**
153
+ * The four look settings each non-custom preset forces. A preset is a LAYER
154
+ * applied on read (see `Config`); it never writes the user's own keys.
155
+ */
156
+ export const PRESETS: Record<Exclude<Preset, "custom">, Record<LookKey, boolean>> = {
157
+ off: { compactToolRows: false, groupToolRuns: false, foldIntermediateActivity: false, thinkingAsToolCall: false },
158
+ lite: { compactToolRows: true, groupToolRuns: false, foldIntermediateActivity: false, thinkingAsToolCall: false },
159
+ full: { compactToolRows: true, groupToolRuns: true, foldIntermediateActivity: false, thinkingAsToolCall: true },
160
+ max: { compactToolRows: true, groupToolRuns: true, foldIntermediateActivity: true, thinkingAsToolCall: true },
161
+ };
162
+
143
163
  export const DEFAULT_ADVANCED: AdvancedSettings = {
144
164
  // `subagent` ships a renderer showing run id, state and a ctrl+o hint that one
145
- // line cannot carry. Excluded by NAME rather than by the old "does this tool
146
- // have its own renderer?" rule, which silently exempted every third-party tool
147
- // and made the behavior impossible to discover from the UI.
165
+ // line cannot carry. Excluded by NAME rather than by a "does this tool
166
+ // have its own renderer?" rule, which would silently exempt every third-party tool.
148
167
  excludeTools: ["subagent"],
149
168
  glyphs: {},
150
169
  tokens: {},
151
- maxDetailChars: 4000,
152
170
  };
153
171
 
154
172
  export const DEFAULTS: Settings = { ...DEFAULT_BASIC, ...DEFAULT_ADVANCED };
@@ -167,6 +185,11 @@ export type BasicKey = keyof BasicSettings;
167
185
  // Live configuration
168
186
  // ---------------------------------------------------------------------------
169
187
 
188
+ /** defaults < the user's own keys < the preset's four look keys (none for `custom`). */
189
+ function layer(user: Settings): Settings {
190
+ return user.preset === "custom" ? user : { ...user, ...PRESETS[user.preset] };
191
+ }
192
+
170
193
  /**
171
194
  * The live settings object.
172
195
  *
@@ -175,7 +198,12 @@ export type BasicKey = keyof BasicSettings;
175
198
  * and no restart.
176
199
  */
177
200
  export class Config {
201
+ /** The user's own values (what settings.json says), before any preset. */
178
202
  private settings: Settings;
203
+ /** What the rest of the extension sees: `settings` with the preset layered on. */
204
+ private get view(): Settings {
205
+ return layer(this.settings);
206
+ }
179
207
 
180
208
  /**
181
209
  * Values changed in this session that are NOT in settings.json.
@@ -192,9 +220,10 @@ export class Config {
192
220
  }
193
221
 
194
222
  get<K extends keyof Settings>(key: K): Settings[K] {
195
- return this.settings[key];
223
+ return this.view[key];
196
224
  }
197
225
 
226
+ /** Writes the user's own value; a non-custom preset keeps shadowing it until `preset` is custom. */
198
227
  set<K extends keyof Settings>(key: K, value: Settings[K]): void {
199
228
  this.settings = { ...this.settings, [key]: value };
200
229
  }
@@ -211,7 +240,7 @@ export class Config {
211
240
  }
212
241
 
213
242
  all(): Settings {
214
- return this.settings;
243
+ return this.view;
215
244
  }
216
245
 
217
246
  /** Adopt freshly read settings, re-applying anything held only in memory. */
@@ -221,26 +250,19 @@ export class Config {
221
250
 
222
251
  /** Preset chosen by `glyphStyle`, then per-glyph overrides. */
223
252
  glyphs(): Glyphs {
224
- return { ...GLYPH_PRESETS[this.settings.glyphStyle], ...this.settings.glyphs };
253
+ return { ...GLYPH_PRESETS[this.view.glyphStyle], ...this.view.glyphs };
225
254
  }
226
255
 
227
256
  /** Default token map plus overrides. */
228
257
  tokens(): Tokens {
229
- return { ...DEFAULT_TOKENS, ...this.settings.tokens };
258
+ return { ...DEFAULT_TOKENS, ...this.view.tokens };
230
259
  }
231
260
 
232
261
  /**
233
- * Should this tool keep Pi's own rendering?
234
- *
235
- * The exclusion list is the ONLY exemption, so the master switch is checked
236
- * alongside it wherever claiming happens.
262
+ * True when this tool should be drawn as a compact row. The exclusion list is
263
+ * the ONLY exemption, so the master switch is checked alongside it.
237
264
  */
238
- isExcluded(toolName: string): boolean {
239
- return this.settings.excludeTools.includes(toolName);
240
- }
241
-
242
- /** True when this tool should be drawn as a compact row. */
243
265
  compacts(toolName: string): boolean {
244
- return this.settings.compactToolRows && !this.isExcluded(toolName);
266
+ return this.view.compactToolRows && !this.view.excludeTools.includes(toolName);
245
267
  }
246
268
  }