From d9f274ec1a66c109e119492515044102c1cbded8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Bene=C5=A1?= Date: Sat, 1 Aug 2026 01:12:02 +0200 Subject: [PATCH] A suggestion card sends its prompt Filling the composer and waiting for Enter made the card a form to review rather than a thing to press. One click, one reply. That changes what a prompt has to be. The built-ins ended mid-sentence -- "My plan: " -- because nothing was sent until the person finished the thought; sent cold they are a model guessing at material nobody gave it. All three are rewritten to ask for what they need, so the first reply is the right question instead. There is a test that they end as complete sentences, since the failure is silent and only visible in the answer. requestSubmit, not submit: it fires the submit event, which is what htmx listens for. Same call the Enter key already makes. Version bumped because app.js is what changed, and the service worker caches it -- without the bump the first load after this would still only fill the box. Co-Authored-By: Claude Opus 5 (1M context) --- pyproject.toml | 2 +- src/lembas/__init__.py | 2 +- src/lembas/db/models/suggestion.py | 6 ++-- src/lembas/services/suggestions.py | 30 ++++++++++--------- src/lembas/web/static/js/app.js | 14 +++++---- .../web/templates/admin/suggestions.html | 12 ++++---- tests/test_suggestions.py | 9 ++++++ 7 files changed, 45 insertions(+), 30 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a69af4..7c331c7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "lembas" -version = "0.3.0" +version = "0.3.1" description = "LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints" readme = "README.md" requires-python = ">=3.11" diff --git a/src/lembas/__init__.py b/src/lembas/__init__.py index 81826b4..7816266 100644 --- a/src/lembas/__init__.py +++ b/src/lembas/__init__.py @@ -1,3 +1,3 @@ """LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints.""" -__version__ = "0.3.0" +__version__ = "0.3.1" diff --git a/src/lembas/db/models/suggestion.py b/src/lembas/db/models/suggestion.py index f47bfe0..4e1192f 100644 --- a/src/lembas/db/models/suggestion.py +++ b/src/lembas/db/models/suggestion.py @@ -20,9 +20,9 @@ class Suggestion(UUIDPrimaryKey, Timestamps, Base): name: Mapped[str] = mapped_column(String(120), nullable=False) description: Mapped[str] = mapped_column(String(300), default="") - # What lands in the composer. Deliberately not sent on its own: it usually - # ends mid-sentence, because a card is a starting point rather than a - # question somebody already asked. + # Sent as the first message the moment the card is clicked, so it has to + # stand on its own -- there is no chance to add anything to it first. The + # built-ins ask for what they need rather than assuming material. prompt: Mapped[str] = mapped_column(Text, default="") enabled: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) diff --git a/src/lembas/services/suggestions.py b/src/lembas/services/suggestions.py index deee617..26cbdea 100644 --- a/src/lembas/services/suggestions.py +++ b/src/lembas/services/suggestions.py @@ -26,31 +26,33 @@ MAX_NAME = 120 MAX_DESCRIPTION = 300 MAX_PROMPT = 4000 -# Each ends mid-sentence, so the caret lands exactly where the person has to -# start typing. No Middle-earth flavour: this is functional UI. +# A card is sent the moment it is clicked, so each of these has to work cold -- +# with nothing pasted and nothing typed. They are written to ask for what they +# need, which turns the first reply into the right question rather than a guess +# at material nobody has given yet. +# +# No Middle-earth flavour: this is functional UI. DEFAULTS: tuple[tuple[str, str, str], ...] = ( ( "Explain this", - "Paste something confusing and get it back in plain language.", - "Explain the following in plain language. Start with one sentence " - "summarising it, then the details that actually matter, then anything I " - "should watch out for. If I have not pasted anything yet, ask me for it " - "rather than guessing.\n\n", + "Get something confusing back in plain language.", + "I want something explained in plain language. Ask me what it is, then " + "give me one sentence summarising it, the details that actually matter, " + "and anything I should watch out for.", ), ( "Draft a reply", "Turn a message you have received into an answer you can send.", - "Help me reply to the message below. If the tone I want and the outcome " - "I am after are not obvious from it, ask me before writing. Then give me " - "a draft I could send as it stands.\n\n", + "I need to reply to a message. Ask me what it says, what tone I want and " + "what outcome I am after, then write a draft I could send as it stands.", ), ( "Find the flaw", "Have a plan argued with before you commit to it.", - "I am going to describe a plan. Argue against it: what is most likely to " - "go wrong, what am I assuming without evidence, and what would change " - "your mind. Do not soften it, and do not agree just because I sound " - "confident.\n\nMy plan: ", + "I want a plan argued with. Ask me what the plan is, then tell me what is " + "most likely to go wrong, what I am assuming without evidence, and what " + "would change your mind. Do not soften it, and do not agree just because " + "I sound confident.", ), ) diff --git a/src/lembas/web/static/js/app.js b/src/lembas/web/static/js/app.js index 88c9b39..692676a 100644 --- a/src/lembas/web/static/js/app.js +++ b/src/lembas/web/static/js/app.js @@ -384,9 +384,11 @@ return; } - /* A suggestion card fills the composer and stops there. It deliberately - does not submit: the prompts end mid-sentence, because a card is a - starting point rather than a question somebody already asked. */ + /* A suggestion card sends its prompt. One click, one reply -- filling the + box and waiting for Enter makes the card a form to review rather than a + thing to press. The built-in prompts are written to work sent cold: each + asks for what it needs, so the answer is a question back rather than a + guess at material nobody has given yet. */ var suggestion = event.target.closest("[data-suggestion]"); if (suggestion) { event.preventDefault(); @@ -394,8 +396,10 @@ if (!input) return; input.value = suggestion.dataset.suggestion; autosize(input); - input.focus(); - input.setSelectionRange(input.value.length, input.value.length); + var form = input.closest("form"); + /* requestSubmit, not submit(): it fires the submit event, which is what + htmx is listening for. Same call the Enter key makes. */ + if (form) form.requestSubmit(); return; } diff --git a/src/lembas/web/templates/admin/suggestions.html b/src/lembas/web/templates/admin/suggestions.html index f46b062..d3d37d0 100644 --- a/src/lembas/web/templates/admin/suggestions.html +++ b/src/lembas/web/templates/admin/suggestions.html @@ -7,9 +7,9 @@ {% block admin_content %}

- Cards on the new-chat screen. Clicking one puts its prompt in the composer - without sending it — the built-in ones deliberately end mid-sentence, so the - caret lands where the person has to start typing. The first + Cards on the new-chat screen. Clicking one sends its prompt straight away, so + each has to work on its own — write one that asks for whatever it needs, and + the first reply becomes the right question rather than a guess. The first {{ max_shown }} enabled ones are shown, in this order.

@@ -64,8 +64,8 @@

- Put in the composer, not sent. Ending it mid-sentence is usually right: - the person still has to say what they are asking about. + Sent as the first message. Nothing is added to it, so a prompt that needs + material should ask for it.

@@ -104,7 +104,7 @@
+ placeholder="Sent as the first message when the card is clicked.">