opencode-ghost 1.0.5 → 1.0.6

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
@@ -5,6 +5,12 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.6] - 2026-10-08
9
+
10
+ ### Fixed
11
+
12
+ - **tui:** Restore retained suggestions when drafts are emptied
13
+
8
14
  ## [1.0.5] - 2026-10-08
9
15
 
10
16
  ### Fixed
@@ -215,6 +221,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
215
221
  - Options for `model`, `acceptKeys`, `maxChars`, `idleDelayMs`,
216
222
  `recentMessages` and `system`.
217
223
 
224
+ [1.0.6]: https://github.com/ozandogrultan/opencode-ghost/compare/v1.0.5...v1.0.6
218
225
  [1.0.5]: https://github.com/ozandogrultan/opencode-ghost/compare/v1.0.4...v1.0.5
219
226
  [1.0.4]: https://github.com/ozandogrultan/opencode-ghost/compare/v1.0.3...v1.0.4
220
227
  [1.0.3]: https://github.com/ozandogrultan/opencode-ghost/compare/v1.0.2...v1.0.3
package/README.md CHANGED
@@ -17,11 +17,14 @@ a dimmed inline placeholder in the empty composer with
17
17
  short transcript of the recent turns, and asks a model for one line that
18
18
  sounds like you.
19
19
  - **Inline empty-composer ghost.** The suggestion renders as the textarea's
20
- dimmed placeholder only while the normal composer is empty and focused in an
21
- idle session. `Tab` or `→` inserts it as editable text; Enter does not accept
20
+ dimmed placeholder while the normal composer is empty in an idle session,
21
+ including when unfocused. With the composer focused, `Tab` or `→` inserts it
22
+ as editable text; Enter does not accept
22
23
  or submit the ghost. Acceptance never replaces a draft or touches the clipboard.
23
- - **Clears as you type.** Text-editing keys and pasting dismiss the suggestion;
24
- navigation keys leave it intact. Left on an empty eligible composer returns
24
+ - **Hides as you type.** Typing and pasting abort pending generation and hide the
25
+ preview. Emptying the composer restores the same suggestion without another
26
+ API call; focus changes also reuse it. Navigation keys leave it intact.
27
+ Left on an empty eligible focused composer returns
25
28
  home, even when suggestions are off (`backOnEmptyLeft`).
26
29
  - **`/suggest` toggles it.** State is stored durably via the plugin's storage.
27
30
 
@@ -137,6 +140,9 @@ When enabling, a suggestion is generated immediately for the current session.
137
140
  - Accepting uses the composer editor's `insertText` directly, without submitting.
138
141
  - Typing, pasting, navigation, a new execution, disabling and unloading cancel
139
142
  pending generation. Duplicate completion events do not generate again.
143
+ - Retains only one suggestion, bounded by `maxChars`, for the visible session.
144
+ Acceptance, a new turn, session changes, disabling, model changes and unloading
145
+ discard it.
140
146
 
141
147
  ## Caveats
142
148
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ghost",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Stateless inline next-prompt ghosts for the empty OpenCode V2 composer, accepted with Tab or Right.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/lifecycle.ts CHANGED
@@ -4,13 +4,13 @@ export function createLifecycle(delay: number, allowed: (sessionID: string) => b
4
4
  let disposed = false
5
5
  let warned = false
6
6
  const completed = new Set<string>()
7
- const cancel = () => {
7
+ const interrupt = () => {
8
8
  clearTimeout(timer)
9
9
  timer = undefined
10
10
  controller?.abort()
11
11
  controller = undefined
12
- clear()
13
12
  }
13
+ const cancel = () => { interrupt(); clear() }
14
14
  const run = async (sessionID: string) => {
15
15
  cancel()
16
16
  if (disposed || !allowed(sessionID)) return
@@ -23,6 +23,7 @@ export function createLifecycle(delay: number, allowed: (sessionID: string) => b
23
23
  }
24
24
  }
25
25
  return {
26
+ interrupt,
26
27
  cancel,
27
28
  run,
28
29
  succeeded(id: string, sessionID: string) {
package/src/tui.tsx CHANGED
@@ -154,7 +154,7 @@ export default Plugin.define({
154
154
  const key = keyName(event)
155
155
  const current = suggestion()
156
156
  return Boolean(current && inline.visible(getComposer(current.sessionID).editor, current.text) && acceptShortcuts(opts.acceptKeys, context.keymap.shortcuts).has(key) && canGenerate(current.sessionID) && isComposerEmpty(current.sessionID))
157
- }, cancelSuggestion)
157
+ }, () => { lifecycle.interrupt(); inline.clear() })
158
158
 
159
159
  const commandLayers = () => ghostKeymapLayers([
160
160
  ...opts.acceptKeys.map((key) => acceptCommand(key, () => {