Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
25aa208d04
|
|||
| 7ebdc9c722 |
@@ -16,6 +16,27 @@ for 1.0.0 have something to be assembled from.
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 1.0.2
|
||||
|
||||
- **The documentation moved to the [wiki](https://git.houmeres.sk/Houmeres/LLeMbas/wiki).**
|
||||
`CLAUDE.md`, `PLAN.md` and `docs/` are gone from the repository: they are
|
||||
documentation *about* this project rather than part of it, and a clone should
|
||||
carry software. Nothing was lost — the working notes, the roadmap and the eight
|
||||
topic notes are all there, with every internal link rewritten, and the README
|
||||
now opens onto them. Where a source comment said "see `CLAUDE.md`" it now says
|
||||
"see the working notes".
|
||||
- Entries below this one still name `PLAN.md` and `docs/notes/…`, and are left as
|
||||
they were written. A changelog records what happened at the time; rewriting old
|
||||
entries to match a later decision makes it a worse record, not a better one.
|
||||
|
||||
## 1.0.1
|
||||
|
||||
- Fixed: the Updates page showed **"v1.0.0 (reports 1.0.0)"** — two spellings of
|
||||
one version, in a note whose whole purpose is to warn that a tag was cut
|
||||
before the version bump. `git describe` answers with the tag's name, and tags
|
||||
here carry a `v`. Found by cutting the first release, which is the only place
|
||||
it could have been.
|
||||
|
||||
## 1.0.0
|
||||
|
||||
The first release. Every version before it shipped as a running deployment
|
||||
|
||||
@@ -144,7 +144,24 @@ runtime. Clone it, `pip install -e .`, run it.
|
||||
|
||||
OCR for scanned PDFs · conversation branching · chat export · archived chats.
|
||||
|
||||
See [PLAN.md](PLAN.md) for what is built, what is not, and why.
|
||||
See the [Roadmap](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Roadmap) for what
|
||||
is built, what is not, and why.
|
||||
|
||||
## Documentation
|
||||
|
||||
The **[wiki](https://git.houmeres.sk/Houmeres/LLeMbas/wiki)** carries everything
|
||||
about how this works and why — it is documentation *about* the project rather
|
||||
than part of it, so a clone stays software.
|
||||
|
||||
- **[Working notes](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Working-notes)**
|
||||
— read this before changing anything. The hard rules the project is built
|
||||
around, the layout, and a long catalogue of *things that will bite you*: bugs
|
||||
that shipped looking correct, why each happened, and what stops it recurring.
|
||||
- **[Roadmap](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Roadmap)** — what is
|
||||
built, what is deliberately not, and the reasoning behind each.
|
||||
- A page each for agent chats, schedules and reports, permissions and sharing,
|
||||
search and extraction, image generation, subagents, branding, and the manual
|
||||
release checklist.
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -479,7 +496,7 @@ python scripts/fetch_vendor.py # verify vendored JS against the lockfile
|
||||
There is no Alembic. The schema is SQLite-only and synchronised at startup:
|
||||
missing tables and missing columns are added automatically, so adding a field to
|
||||
a model needs nothing but a restart. Renames, drops and retypes are still manual
|
||||
— see `CLAUDE.md`.
|
||||
— see the [working notes](https://git.houmeres.sk/Houmeres/LLeMbas/wiki/Working-notes).
|
||||
|
||||
## Artwork
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""LLeMbas - a Middle-earth themed web UI for OpenAI-compatible LLM endpoints."""
|
||||
|
||||
__version__ = "1.0.0"
|
||||
__version__ = "1.0.2"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Two shapes on one nav entry, because they are two different kinds of thing. The
|
||||
connection, the checkpoints and the switches are instance settings and get a
|
||||
settings page. A workflow is an authored document with a name, a description and
|
||||
a body, so the workflows are list-plus-detail -- the shape `CLAUDE.md` requires
|
||||
a body, so the workflows are list-plus-detail -- the shape the working notes require
|
||||
of any admin list, and for the reason it gives: a page that renders a ten-line
|
||||
JSON textarea per row is unusable at three rows.
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ async def browse_profile(
|
||||
it holds for the same reason -- somebody who owns the credential could list
|
||||
the directory with an ssh client -- but it does mean Manual mode's promise
|
||||
that everything is shown to you first now has a second exception. Both are
|
||||
written down in CLAUDE.md.
|
||||
written down in the working notes.
|
||||
"""
|
||||
profile = _profile(db, user, profile_id)
|
||||
entries: list = []
|
||||
|
||||
@@ -985,7 +985,7 @@ def _too_many_replies(db: DBSession, chat: Chat, user: User) -> str:
|
||||
"""Why this account may not start another reply right now, or "".
|
||||
|
||||
In-process, and that is exact rather than approximate only because this
|
||||
application runs one worker -- see the first known limit in PLAN.md. With
|
||||
application runs one worker -- see the first known limit in the roadmap. With
|
||||
several, this becomes a guess, and a quota that is a guess should be a
|
||||
number in the database instead. Stated here rather than discovered.
|
||||
"""
|
||||
|
||||
+1
-1
@@ -263,7 +263,7 @@ def register_error_handlers(app: FastAPI) -> None:
|
||||
|
||||
|
||||
# Flavour lives in error pages, empty states and theme names -- never in the
|
||||
# functional UI. See CLAUDE.md.
|
||||
# functional UI. See the working notes.
|
||||
#
|
||||
# The three lines themselves moved into `services/branding.py` with the rest of
|
||||
# what an administrator can replace. What is left here is the mapping from a
|
||||
|
||||
@@ -18,7 +18,7 @@ a model choosing to run something. Both are read-only, both are built here
|
||||
rather than assembled from anything a model said, and the project directory is
|
||||
configuration rather than input. It is still an exception to Manual mode's
|
||||
"everything is shown to you before it happens", and it is written down in
|
||||
CLAUDE.md next to the others.
|
||||
the working notes, next to the others.
|
||||
|
||||
**Nothing here is trusted.** Filenames come off somebody else's machine and end
|
||||
up inside a system prompt, so they are stripped of control characters, capped
|
||||
|
||||
@@ -184,7 +184,7 @@ def launch_and_wait_command(chat_id: str, job_id: str, command: str, max_bytes:
|
||||
# operand and formats to "<Logger … (WARNING)>", whose angle brackets and
|
||||
# parentheses are shell syntax -- so this line died with a syntax error,
|
||||
# after the sentinel where nothing reads it, and every job's four files
|
||||
# were left on the far side forever. See the note in CLAUDE.md.
|
||||
# were left on the far side forever. See the note in the working notes.
|
||||
f"rm -f {_file(chat_id, job_id, 'sh')} {pid} {logf} {exit_}\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ are separate because they fail differently:
|
||||
shows;
|
||||
- **flavour text** — the Middle-earth lines, which live in the artwork, the
|
||||
empty states, the loading lines and the error pages and nowhere else (see the
|
||||
flavour rule in CLAUDE.md), and which somebody rebranding needs to be able to
|
||||
flavour rule in the working notes), and which somebody rebranding needs to be able to
|
||||
replace without editing templates;
|
||||
- **themes**, which are token sets rather than stylesheets, because the
|
||||
invariant that no component hard-codes a colour is what makes a third one
|
||||
@@ -36,7 +36,7 @@ page and by nothing else.
|
||||
The cost of being a cache is stated rather than discovered: with several
|
||||
workers, a save in one is not seen by the others until each next reads. That is
|
||||
already true of this application for other reasons -- see the "one worker" note
|
||||
in PLAN.md -- and this does not make it worse.
|
||||
in the roadmap -- and this does not make it worse.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -383,7 +383,7 @@ async def _run_fetch(context: ToolContext, args: dict[str, Any]) -> ToolOutcome:
|
||||
|
||||
Straight through `services/fetch.py`, which owns the SSRF guard, the
|
||||
hand-rolled redirect loop that re-checks every hop, and the content-type
|
||||
sniff. Deliberately not a second HTTP client: CLAUDE.md already names three
|
||||
sniff. Deliberately not a second HTTP client: the working notes already name three
|
||||
places that follow redirects by hand as the ceiling, and a fourth is how one
|
||||
of them loses its check.
|
||||
"""
|
||||
|
||||
@@ -340,7 +340,7 @@ def resolve_target(root: Path, channel: str, branch: str) -> Target | None:
|
||||
return None
|
||||
return Target(
|
||||
ref=tag,
|
||||
label=tag.lstrip("v"),
|
||||
label=tag.removeprefix("v"),
|
||||
sha=commit.sha,
|
||||
subject=commit.subject,
|
||||
notes=_notes_for(root, tag),
|
||||
@@ -362,9 +362,16 @@ def _describe(root: Path) -> str:
|
||||
bare short sha when nothing has ever been tagged. That last case is why
|
||||
`--always` is there: without it this fails outright on a repository with no
|
||||
tags, which is every repository before its first release.
|
||||
|
||||
The leading `v` comes off, because git answers with the **tag's name** and
|
||||
tags here are `v1.0.0` while `__version__` is `1.0.0`. Without this the page
|
||||
read "v1.0.0 (reports 1.0.0)" -- a note whose whole purpose is to flag a tag
|
||||
cut before a version bump, firing on two spellings of the same version. The
|
||||
first release is what showed it. `resolve_target` has always stripped it for
|
||||
the same reason, and this docstring already promised the stripped form.
|
||||
"""
|
||||
code, output = _git(["describe", "--tags", "--always", "--dirty="], cwd=root)
|
||||
return output if code == 0 else ""
|
||||
return output.removeprefix("v") if code == 0 else ""
|
||||
|
||||
|
||||
def read(*, fetch: bool = False) -> State:
|
||||
@@ -418,8 +425,8 @@ def read(*, fetch: bool = False) -> State:
|
||||
# Exactly at a tag whose name disagrees with the version this process
|
||||
# reports. No subprocess: `running` and `__version__` are both already here.
|
||||
mismatch = ""
|
||||
if RELEASE_TAG.match(running) and running.lstrip("v") != __version__:
|
||||
mismatch = running.lstrip("v")
|
||||
if RELEASE_TAG.match(running) and running != __version__:
|
||||
mismatch = running
|
||||
|
||||
return State(
|
||||
**base,
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
toggling their panel twice, which is to say doing nothing at all.
|
||||
|
||||
None of it looks like a script loaded twice. Found by driving the file under a
|
||||
DOM stub, which is the rule `CLAUDE.md` sets out and the reason it does.
|
||||
DOM stub, which is the rule the working notes set out and the reason it does.
|
||||
#}
|
||||
{% block scripts %}
|
||||
<script src="{{ url_for('static', path='js/steps.js') }}" defer></script>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
Cached at install time, so it has to stand entirely on its own: no user, no
|
||||
chats, nothing that was rendered from the database. One of the few places
|
||||
flavour belongs -- see the flavour rule in CLAUDE.md.
|
||||
flavour belongs -- see the flavour rule in the working notes.
|
||||
#}
|
||||
|
||||
{% block title %}Offline - {{ brand.name }}{% endblock %}
|
||||
|
||||
@@ -509,3 +509,39 @@ def test_a_reinstall_does_not_move_the_channel_by_itself():
|
||||
assert 'CHANNEL="${LEMBAS_CHANNEL:-${_installed_channel:-stable}}"' in install
|
||||
# Parsed rather than sourced: that file holds the secret key.
|
||||
assert ". $PREFIX/lembas.env" not in install
|
||||
|
||||
|
||||
def test_the_running_version_is_not_two_spellings_of_itself(db, tagged):
|
||||
"""`git describe` answers with the **tag's name**, and tags here are
|
||||
`v1.0.0` while `__version__` is `1.0.0`. The page prints the described
|
||||
version and appends "(reports X)" when the two disagree -- a note whose
|
||||
whole purpose is to flag a tag cut *before* a version bump.
|
||||
|
||||
Unstripped, it fired on the first release: "v9.9.9 (reports 9.9.9)", which
|
||||
reads as a discrepancy and is two ways of writing one version. Found by
|
||||
cutting the release, which is the only place it could have been.
|
||||
"""
|
||||
root = updates.checkout_dir()
|
||||
running = updates._describe(root)
|
||||
|
||||
assert not running.startswith("v"), running
|
||||
assert running.startswith("9.9.9"), running
|
||||
|
||||
|
||||
def test_a_tag_that_really_disagrees_is_still_reported(db):
|
||||
"""The note has to keep working, or stripping the prefix has removed the
|
||||
check rather than fixed it. A tag cut before the version bump names a
|
||||
release nobody can identify afterwards."""
|
||||
import subprocess
|
||||
|
||||
root = updates.checkout_dir()
|
||||
subprocess.run(
|
||||
["git", "tag", "-a", "v99.0.0", "-m", "Wrong."],
|
||||
cwd=root, check=True, capture_output=True,
|
||||
)
|
||||
try:
|
||||
assert updates.read().version_mismatch == "99.0.0"
|
||||
finally:
|
||||
subprocess.run(
|
||||
["git", "tag", "-d", "v99.0.0"], cwd=root, check=False, capture_output=True
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user