pict-section-prompteditor 1.0.0 → 1.1.1

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/README.md CHANGED
@@ -9,6 +9,7 @@ A prompt crafting and management section for the [Pict](https://github.com/fable
9
9
  ## Features
10
10
 
11
11
  - Typed prompts with markdown segments (Context / Request / Success Criteria and friends), plus **fixed preamble** segments for locked team standards
12
+ - A per-prompt "segment headings in the output" toggle, saved with the prompt: on for structured documents (`## Context` above each block), off for bare bodies
12
13
  - Weighted word list matrices: `[["Tyrannosaurus", 3], ["Diplodocus", 1]]` draws 75/25, with live share percentages as you curate
13
14
  - The `{~WordListEntry:Name~}` template expression (short form `{~WLE:~}`), with an optional miss default: `{~WLE:Name:fallback~}`
14
15
  - Generation runs the **real pict template engine**, so every pict expression works inside a prompt
package/package.json CHANGED
@@ -1,45 +1,45 @@
1
1
  {
2
- "name": "pict-section-prompteditor",
3
- "version": "1.0.0",
4
- "description": "Prompt crafting and management section for Pict: typed prompts with segments (context, request, success criteria), curated word list matrices with weights, a {~WordListEntry:Name~} template expression for weighted-random generation, batch generation with zip download, and a pluggable data provider that defaults to in-memory AppData.",
5
- "main": "source/Pict-Section-PromptEditor.js",
6
- "files": [
7
- "source"
8
- ],
9
- "repository": {
10
- "type": "git",
11
- "url": "git+https://github.com/fable-retold/pict-section-prompteditor.git"
12
- },
13
- "scripts": {
14
- "test": "npx mocha -u tdd -R spec --exit test/*_tests.js",
15
- "start": "node source/Pict-Section-PromptEditor.js",
16
- "build": "npx quack build",
17
- "build:codemirror": "node build/build-codemirror-bundle.js",
18
- "example": "npx quack examples",
19
- "docs": "npx quack prepare-docs ./docs"
20
- },
21
- "author": "steven velozo <steven@velozo.com>",
22
- "license": "MIT",
23
- "dependencies": {
24
- "jszip": "^3.10.1",
25
- "pict-section-content": "^1.0.15",
26
- "pict-section-markdowneditor": "^1.0.17",
27
- "pict-section-modal": "^1.1.4",
28
- "pict-section-picker": "^1.4.0",
29
- "pict-template": "^1.0.15",
30
- "pict-view": "^1.0.68"
31
- },
32
- "devDependencies": {
33
- "@codemirror/lang-markdown": "^6.5.0",
34
- "@codemirror/state": "^6.6.0",
35
- "@codemirror/view": "^6.43.0",
36
- "chai": "^4.5.0",
37
- "codemirror": "^6.0.2",
38
- "esbuild": "^0.28.0",
39
- "jsdom": "^25.0.1",
40
- "mocha": "^11.0.1",
41
- "pict": "^1.0.381",
42
- "pict-docuserve": "^1.4.19",
43
- "quackage": "^1.3.0"
44
- }
2
+ "name": "pict-section-prompteditor",
3
+ "version": "1.1.1",
4
+ "description": "Prompt crafting and management section for Pict: typed prompts with segments (context, request, success criteria), curated word list matrices with weights, a {~WordListEntry:Name~} template expression for weighted-random generation, batch generation with zip download, and a pluggable data provider that defaults to in-memory AppData.",
5
+ "main": "source/Pict-Section-PromptEditor.js",
6
+ "files": [
7
+ "source"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/fable-retold/pict-section-prompteditor.git"
12
+ },
13
+ "scripts": {
14
+ "test": "npx mocha -u tdd -R spec --exit test/*_tests.js",
15
+ "start": "node source/Pict-Section-PromptEditor.js",
16
+ "build": "npx quack build",
17
+ "build:codemirror": "node build/build-codemirror-bundle.js",
18
+ "example": "npx quack examples",
19
+ "docs": "npx quack prepare-docs ./docs"
20
+ },
21
+ "author": "steven velozo <steven@velozo.com>",
22
+ "license": "MIT",
23
+ "dependencies": {
24
+ "jszip": "^3.10.1",
25
+ "pict-section-content": "^1.0.15",
26
+ "pict-section-markdowneditor": "^1.0.17",
27
+ "pict-section-modal": "^1.1.4",
28
+ "pict-section-picker": "^1.4.0",
29
+ "pict-template": "^1.0.15",
30
+ "pict-view": "^1.0.68"
31
+ },
32
+ "devDependencies": {
33
+ "@codemirror/lang-markdown": "^6.5.0",
34
+ "@codemirror/state": "^6.6.0",
35
+ "@codemirror/view": "^6.43.0",
36
+ "chai": "^4.5.0",
37
+ "codemirror": "^6.0.2",
38
+ "esbuild": "^0.28.0",
39
+ "jsdom": "^25.0.1",
40
+ "mocha": "^11.0.1",
41
+ "pict": "^1.0.381",
42
+ "pict-docuserve": "^1.4.19",
43
+ "quackage": "^1.3.0"
44
+ }
45
45
  }
@@ -31,6 +31,12 @@ function slug(pText)
31
31
 
32
32
  /**
33
33
  * Assemble a prompt's markdown source from its type's segments.
34
+ *
35
+ * Segment headings (## Context and friends) are on by default; a prompt that
36
+ * carries IncludeSegmentHeadings: false compiles bare segment bodies instead,
37
+ * so the author decides per prompt whether the structure shows in the output.
38
+ * The document title heading stays governed by the IncludeTitleHeading option.
39
+ *
34
40
  * @param {object} pPrompt - the prompt record
35
41
  * @param {object} pType - the resolved prompt type
36
42
  * @param {object} [pOptions] - { IncludeTitleHeading: true, SegmentHeadingLevel: 2 }
@@ -40,6 +46,7 @@ function assembleSource(pPrompt, pType, pOptions)
40
46
  {
41
47
  let tmpOptions = pOptions || {};
42
48
  let tmpIncludeTitle = (typeof tmpOptions.IncludeTitleHeading === 'undefined') ? true : !!tmpOptions.IncludeTitleHeading;
49
+ let tmpIncludeSegmentHeadings = !(pPrompt && pPrompt.IncludeSegmentHeadings === false);
43
50
  let tmpHeading = '#'.repeat(Math.max(1, Math.min(6, Number(tmpOptions.SegmentHeadingLevel) || 2)));
44
51
  let tmpSegments = (pType && Array.isArray(pType.Segments)) ? pType.Segments : [];
45
52
  let tmpBodies = (pPrompt && pPrompt.Segments) ? pPrompt.Segments : {};
@@ -60,7 +67,9 @@ function assembleSource(pPrompt, pType, pOptions)
60
67
  if (tmpSegment.Optional || tmpSegment.Fixed) { continue; }
61
68
  tmpBody = '(nothing written for this segment yet)';
62
69
  }
63
- tmpParts.push(tmpHeading + ' ' + String(tmpSegment.Name || tmpSegment.Key) + '\n\n' + tmpBody);
70
+ tmpParts.push(tmpIncludeSegmentHeadings
71
+ ? (tmpHeading + ' ' + String(tmpSegment.Name || tmpSegment.Key) + '\n\n' + tmpBody)
72
+ : tmpBody);
64
73
  }
65
74
 
66
75
  return tmpParts.join('\n\n') + '\n';
@@ -16,9 +16,11 @@
16
16
  * drawn by {~WordListEntry:Name~} is weight / sum(weights).
17
17
  *
18
18
  * Prompt { Key, Title, TypeKey, Segments: { segmentKey: markdown },
19
- * Author, Meta, CreatedAt, UpdatedAt }
19
+ * IncludeSegmentHeadings, Author, Meta, CreatedAt, UpdatedAt }
20
20
  * A crafted prompt. Segments hold the markdown for each of the prompt
21
- * type's segments, keyed by segment Key. Meta is an opaque object the
21
+ * type's segments, keyed by segment Key. IncludeSegmentHeadings (default
22
+ * true) decides whether the compiler writes the ## segment headings above
23
+ * each body in the assembled output. Meta is an opaque object the
22
24
  * provider round-trips untouched -- the seam where a host hangs ratings,
23
25
  * version pointers, or anything else.
24
26
  *
@@ -208,6 +210,7 @@ class InMemoryPromptProvider extends PromptDataProvider
208
210
  Title: String(tmpDraft.Title || 'Untitled prompt'),
209
211
  TypeKey: String(tmpDraft.TypeKey),
210
212
  Segments: (tmpDraft.Segments && typeof tmpDraft.Segments === 'object') ? this._clone(tmpDraft.Segments) : {},
213
+ IncludeSegmentHeadings: (tmpDraft.IncludeSegmentHeadings === false) ? false : true,
211
214
  Author: tmpDraft.Author || null,
212
215
  Meta: (typeof tmpDraft.Meta === 'undefined') ? {} : this._clone(tmpDraft.Meta),
213
216
  CreatedAt: tmpNow,
@@ -225,6 +228,7 @@ class InMemoryPromptProvider extends PromptDataProvider
225
228
  if (typeof tmpPatch.Title !== 'undefined') { tmpPrompt.Title = String(tmpPatch.Title); }
226
229
  if (typeof tmpPatch.TypeKey !== 'undefined') { tmpPrompt.TypeKey = String(tmpPatch.TypeKey); }
227
230
  if (typeof tmpPatch.Segments !== 'undefined') { tmpPrompt.Segments = this._clone(tmpPatch.Segments || {}); }
231
+ if (typeof tmpPatch.IncludeSegmentHeadings !== 'undefined') { tmpPrompt.IncludeSegmentHeadings = (tmpPatch.IncludeSegmentHeadings === false) ? false : true; }
228
232
  if (typeof tmpPatch.Meta !== 'undefined') { tmpPrompt.Meta = this._clone(tmpPatch.Meta); }
229
233
  tmpPrompt.UpdatedAt = this._now();
230
234
  return Promise.resolve(this._clone(tmpPrompt));
@@ -83,13 +83,25 @@ const _DefaultConfiguration =
83
83
  CSS: /*css*/`
84
84
  .pspe { font-size: 14px; color: var(--theme-color-text-primary, #1f2430); }
85
85
  .pspe-header { display: flex; align-items: center; gap: 14px; margin-bottom: 12px; flex-wrap: wrap; }
86
- .pspe-title { font-size: 16px; font-weight: 650; margin-right: auto; }
86
+ /* Structural titles take the theme's optional display face + treatment.
87
+ The fallbacks (inherit / none / normal / 650) keep the look unchanged
88
+ for hosts whose theme does not define a display family. A host whose
89
+ brand carries one (e.g. a drafting/engraving face) gets it here with
90
+ no app-side override. */
91
+ .pspe-title { font-size: 16px; margin-right: auto;
92
+ font-family: var(--theme-typography-family-display, inherit);
93
+ text-transform: var(--theme-typography-display-transform, none);
94
+ letter-spacing: var(--theme-typography-display-tracking, normal);
95
+ font-weight: var(--theme-typography-display-weight, 650); }
87
96
  .pspe-tabs { display: flex; gap: 4px; }
88
97
  .pspe-tab {
89
98
  font: inherit; font-size: 13px; padding: 6px 12px; cursor: pointer;
90
99
  border: 1px solid var(--theme-color-border-default, #d6dde3);
91
100
  background: var(--theme-color-background-panel, #fff);
92
101
  color: var(--theme-color-text-secondary, #5b6470); border-radius: 8px;
102
+ font-family: var(--theme-typography-family-display, inherit);
103
+ text-transform: var(--theme-typography-display-transform, none);
104
+ letter-spacing: var(--theme-typography-display-tracking, normal);
93
105
  }
94
106
  .pspe-tab-on { background: var(--theme-color-brand-primary, #2880a6); border-color: var(--theme-color-brand-primary, #2880a6); color: var(--theme-color-text-on-brand, #fff); }
95
107
  .pspe-tab-count { opacity: 0.75; font-size: 11px; margin-left: 4px; }
@@ -136,13 +148,19 @@ const _DefaultConfiguration =
136
148
  .pspe-editor-head { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
137
149
  .pspe-editor-title { flex: 1; min-width: 200px; font-weight: 650; font-size: 15px; }
138
150
  .pspe-type-desc { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin: -6px 0 12px; }
151
+ .pspe-editor-opts { margin: -4px 0 12px; }
152
+ .pspe-editor-opt { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); cursor: pointer; user-select: none; }
139
153
 
140
154
  .pspe-segment {
141
155
  border: 1px solid var(--theme-color-border-light, #e7ecf0); border-radius: 10px;
142
156
  background: var(--theme-color-background-panel, #fff); padding: 12px 14px; margin-bottom: 10px;
143
157
  }
144
158
  .pspe-segment-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
145
- .pspe-segment-name { font-weight: 650; font-size: 13.5px; }
159
+ .pspe-segment-name { font-size: 13.5px;
160
+ font-family: var(--theme-typography-family-display, inherit);
161
+ text-transform: var(--theme-typography-display-transform, none);
162
+ letter-spacing: var(--theme-typography-display-tracking, normal);
163
+ font-weight: var(--theme-typography-display-weight, 650); }
146
164
  .pspe-segment-guidance { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin-right: auto; }
147
165
  .pspe-pill {
148
166
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em; padding: 1px 8px; border-radius: 999px;
@@ -170,7 +188,11 @@ const _DefaultConfiguration =
170
188
  .pspe-generate-note { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin-left: auto; }
171
189
  .pspe-preview-panel { margin-top: 12px; }
172
190
  .pspe-preview-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
173
- .pspe-preview-title { font-weight: 650; font-size: 13.5px; }
191
+ .pspe-preview-title { font-size: 13.5px;
192
+ font-family: var(--theme-typography-family-display, inherit);
193
+ text-transform: var(--theme-typography-display-transform, none);
194
+ letter-spacing: var(--theme-typography-display-tracking, normal);
195
+ font-weight: var(--theme-typography-display-weight, 650); }
174
196
  .pspe-preview-note { font-size: 12px; color: var(--theme-color-text-muted, #97a1ab); margin-right: auto; }
175
197
  .pspe-btn-on, .pspe-btn-on:hover { background: var(--theme-color-brand-primary, #2880a6); border-color: var(--theme-color-brand-primary, #2880a6); color: var(--theme-color-text-on-brand, #fff); }
176
198
  .pspe-preview-raw {
@@ -254,6 +276,7 @@ const _DefaultConfiguration =
254
276
  {~TS:PromptEditor-PromptActions:Record.ActionsSlot~}
255
277
  </div>
256
278
  <div class="pspe-type-desc">{~D:Record.TypeDescription~}</div>
279
+ {~TS:PromptEditor-EditorOptions:Record.OptionsSlot~}
257
280
  {~TS:PromptEditor-Segment:Record.Segments~}
258
281
  {~TS:PromptEditor-GenerateBar:Record.GenerateSlot~}
259
282
  {~TS:PromptEditor-PreviewPanel:Record.PreviewPanelSlot~}`
@@ -282,6 +305,17 @@ const _DefaultConfiguration =
282
305
  Hash: 'PromptEditor-PromptActions',
283
306
  Template: /*html*/`<button class="pspe-btn pspe-btn-sm" title="Duplicate this prompt" onclick="_Pict.views['{~D:Record.ViewHash~}'].duplicatePrompt()">Duplicate</button><button class="pspe-btn pspe-btn-sm pspe-btn-danger" onclick="_Pict.views['{~D:Record.ViewHash~}'].deletePrompt()">Delete</button>`
284
307
  },
308
+ {
309
+ Hash: 'PromptEditor-EditorOptions',
310
+ Template: /*html*/`
311
+ <div class="pspe-editor-opts">
312
+ <label class="pspe-editor-opt" title="When off, the assembled prompt is just the segment bodies, no ## headings above them.">
313
+ <input type="checkbox" {~D:Record.SegmentHeadingsChecked~}
314
+ onchange="_Pict.views['{~D:Record.ViewHash~}'].setSegmentHeadings(this.checked)">
315
+ Segment headings in the output
316
+ </label>
317
+ </div>`
318
+ },
285
319
  {
286
320
  Hash: 'PromptEditor-Segment',
287
321
  Template: /*html*/`
@@ -909,6 +943,11 @@ class PictViewPromptEditor extends libPictView
909
943
  SelectedAttr: (pTypeOption.Key === pPrompt.TypeKey) ? 'selected' : ''
910
944
  })),
911
945
  ActionsSlot: pReadOnly ? [] : [{ ViewHash: this.Hash }],
946
+ OptionsSlot: pReadOnly ? [] :
947
+ [{
948
+ ViewHash: this.Hash,
949
+ SegmentHeadingsChecked: (pPrompt.IncludeSegmentHeadings === false) ? '' : 'checked'
950
+ }],
912
951
  Segments: tmpSegments,
913
952
  GenerateSlot: pReadOnly ? [] :
914
953
  [{
@@ -1077,6 +1116,7 @@ class PictViewPromptEditor extends libPictView
1077
1116
  TypeKey: tmpPrompt.TypeKey,
1078
1117
  Title: tmpPrompt.Title + ' (copy)',
1079
1118
  Segments: JSON.parse(JSON.stringify(tmpPrompt.Segments || {})),
1119
+ IncludeSegmentHeadings: tmpPrompt.IncludeSegmentHeadings !== false,
1080
1120
  Meta: JSON.parse(JSON.stringify(tmpPrompt.Meta || {})),
1081
1121
  Author: this.options.CurrentUser
1082
1122
  })
@@ -1123,6 +1163,24 @@ class PictViewPromptEditor extends libPictView
1123
1163
  .catch((pError) => this._toast('Segment save failed: ' + pError.message, 'error'));
1124
1164
  }
1125
1165
 
1166
+ // The per-prompt "segment headings in the output" toggle: saves with the
1167
+ // prompt, and the compiler reads it on every assemble (preview, generate,
1168
+ // zip). No re-render needed - the checkbox itself holds the new state -
1169
+ // but an open preview re-rolls so what you see matches what you would get.
1170
+ setSegmentHeadings(pChecked)
1171
+ {
1172
+ let tmpPrompt = this._activePrompt();
1173
+ if (!tmpPrompt) { return; }
1174
+ tmpPrompt.IncludeSegmentHeadings = !!pChecked;
1175
+ this._provider.updatePrompt(tmpPrompt.Key, { IncludeSegmentHeadings: !!pChecked })
1176
+ .then((pSaved) =>
1177
+ {
1178
+ this._fire('onPromptSaved', pSaved);
1179
+ if (this._ui.Preview && this._ui.Preview.PromptKey === tmpPrompt.Key) { this.previewOnce(); }
1180
+ })
1181
+ .catch((pError) => this._toast('Save failed: ' + pError.message, 'error'));
1182
+ }
1183
+
1126
1184
  toggleSegmentPreview(pSegmentKey)
1127
1185
  {
1128
1186
  let tmpPrompt = this._activePrompt();