dce54eb2de
The version, the changelog entry, the plan and the README. Nothing else, which is what makes this readable as a release rather than as work. CHANGELOG.md's 1.0.0 entry is assembled from every version below it, as that file has said it would be since it was written: those shipped as a running deployment rather than as releases, and this is what they add up to. It is also what an administrator reads -- /admin/updates takes release notes out of the annotated tag, so the tag message is this entry. It says what arrived, then the part worth reading: the nine things that had shipped looking correct and were found by five audit passes. Then where the edges are, because a first release should say what it does not do before somebody finds out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
438 lines
24 KiB
Markdown
438 lines
24 KiB
Markdown
# Changelog
|
|
|
|
What changed, per version, for somebody using or running LLeMbas — not a
|
|
restatement of the commit log. If a change fixed something that *looked* like it
|
|
worked, that is worth a line: those are the ones nobody would otherwise know to
|
|
stop working around.
|
|
|
|
Newest first. Versions are `__version__` in `src/lembas/__init__.py`, which is
|
|
the only place a version is written.
|
|
|
|
The first tagged release is **1.0.0**. Everything below it shipped as a running
|
|
deployment rather than as a release, and is recorded here so the release notes
|
|
for 1.0.0 have something to be assembled from.
|
|
|
|
---
|
|
|
|
## Unreleased
|
|
|
|
## 1.0.0
|
|
|
|
The first release. Every version before it shipped as a running deployment
|
|
rather than as a release; this is what those add up to, and the point at which
|
|
it is worth somebody else installing.
|
|
|
|
**What it is.** A self-hosted web interface for OpenAI-compatible endpoints.
|
|
Server-rendered, no build step, no CDN, one SQLite file. Point it at whatever
|
|
you run — llama.cpp, LM Studio, vLLM, Ollama, OpenRouter, OpenAI — and it works
|
|
the same.
|
|
|
|
### What arrived since 0.8.1
|
|
|
|
- **Things that happen because time passed.** Say "every Monday at nine" and a
|
|
model sets it up itself, against the same recurrence rule the manual form
|
|
uses. A run can file a **report** you read later, send you a message, or work
|
|
in a chat of its own.
|
|
- **News that finds you.** A dot in the sidebar, a count in the tab title while
|
|
you are looking elsewhere, and **web push** so a schedule firing at seven in
|
|
the morning reaches a browser that is shut. Opt-in per device, and the one
|
|
thing here that contacts an outside service — `services/push.py` says so
|
|
plainly and says what it costs.
|
|
- **Helpers.** A reply can hand a self-contained piece of work to another model
|
|
that runs on its own and reports back, several at once. A helper cannot ask
|
|
questions, cannot send helpers of its own, changes nothing unless asked, and
|
|
on a machine runs only a fixed list of read-only commands.
|
|
- **Drawing.** Point it at a ComfyUI and a model can make images, against
|
|
workflow templates and defaults you set — size, steps, sampler, scheduler,
|
|
checkpoint. It reviews its own result and can try again.
|
|
- **Semantic search.** Pick an embedding model and library search fuses keyword
|
|
and meaning, so *"how do I get paid"* finds a document that says *"invoicing"*.
|
|
Choosing none is not a degraded mode: it is byte-for-byte the keyword search
|
|
that was always there, with nothing written and no requests made.
|
|
- **Quotas and sharing.** Monthly tokens, concurrent replies, agent wall clock,
|
|
images a day, helpers a reply — resolved by maximum across a person's groups,
|
|
with zero meaning *no limit*. Documents, notes, skills and reports can be
|
|
handed to a group or a person, read-only, with a *Shared with me* filter
|
|
everywhere. And a screen that answers **"what can this account actually do?"**
|
|
by naming where each permission came from.
|
|
- **Make it yours.** Name, tagline, logo, favicon and launcher icons; the
|
|
Middle-earth wording is editable data; custom themes defined as a set of
|
|
colours rather than a stylesheet.
|
|
- **Install it and update it.** A Dockerfile, a Proxmox container script, and an
|
|
`/admin/updates` page showing what is running, what is available and what
|
|
changed between. The button that applies an update is opt-in and cannot do the
|
|
work itself — it writes a file that a systemd unit picks up, because a web
|
|
application that can restart its own service is one whose worst day is much
|
|
worse.
|
|
|
|
### The part worth reading
|
|
|
|
Five audit passes went into this release rather than one, and they found things
|
|
that had shipped looking correct. These are the entries somebody stops working
|
|
around a bug because of:
|
|
|
|
- **Every model was told the time in a zone with no name** — on any account that
|
|
had not chosen one, which is every account by default.
|
|
- **A helper could write files and run programs on a remote machine,
|
|
unattended, in a mode that promises to change nothing.** `find` was on the
|
|
read-only command list, and `find -fprintf` writes a file.
|
|
- **Two ways to get root out of the update helper**, one of which needed no
|
|
compromise at all: root ran a script the unprivileged service account owns,
|
|
and an update fetches that script as that account.
|
|
- **Deleting a chat left every file it held on disk** — attachments, generated
|
|
images, all of it, with nothing that would ever look at them again.
|
|
- **Folder nesting was fully built, documented in the README, and reachable by
|
|
nothing.** So was moving a chat into a folder.
|
|
- **The terminal silently stopped accepting input after a reconnect**, while
|
|
output kept arriving so the panel looked healthy.
|
|
- **On the Messages screen, half the keyboard shortcuts did nothing**, because
|
|
two scripts were loaded twice and each toggle ran twice.
|
|
- **The prompt preview could not show two thirds of what it previews.**
|
|
- **Hints and timestamps failed the contrast minimum in both themes.**
|
|
|
|
### Where the edges are
|
|
|
|
Stated because they are the things worth knowing before you rely on it:
|
|
|
|
- **Nothing executes on the machine LLeMbas runs on.** Agent chats run their
|
|
commands over SSH on a host you choose, and the security of an agent chat is
|
|
the security of that host. There is no sandbox here and that is deliberate —
|
|
`PLAN.md` records the one that was designed and dropped, and why.
|
|
- **One worker.** The generation registry, the terminal sessions and the
|
|
schedule ticker are all in-process. Two workers means two tickers and every
|
|
schedule firing twice.
|
|
- **A restart abandons replies in flight**, keeping whatever each had.
|
|
- **Schema changes are additive.** New tables and columns apply themselves at
|
|
startup; renames and drops are manual. The upgrade path is tested from an
|
|
0.8.1-shaped database with rows in it.
|
|
- **Sharing grants reading only.**
|
|
|
|
2283 tests on Python 3.11, 3.12 and 3.14.
|
|
|
|
## 0.9.13
|
|
|
|
**The testing pass.** 2140 tests became 2283, and writing them found four bugs
|
|
that no amount of reading had.
|
|
|
|
- Fixed: **the terminal silently stopped accepting input after a reconnect.**
|
|
Change the connection, or let the shell catch up after falling behind, and
|
|
every keystroke was dropped from then on — while output kept arriving, so the
|
|
panel looked perfectly healthy. It also announced "Disconnected. Close and
|
|
reopen to reconnect." about a shell that had just reconnected successfully.
|
|
- Fixed: **on the Messages screen, half the keyboard did nothing.** Two scripts
|
|
were loaded twice there, so `Alt+B`, `Alt+E`, `Alt+T` and `Alt+I` toggled
|
|
their panel twice — which is to say not at all — while `/help` opened two
|
|
dialogs, `/image` posted the message twice, and picking an `@` mention
|
|
attached the file twice.
|
|
- Fixed: **pressing the microphone while the permission prompt was up opened a
|
|
recording each time.** Only the last was stopped, so the browser's recording
|
|
indicator stayed on until the tab was closed.
|
|
- Fixed: **a skill shared with you took its name out of your own library.**
|
|
Creating your own was refused with "a skill called that already exists. Edit
|
|
it instead" — naming a skill you cannot edit, because sharing grants reading
|
|
only. The model's `skill_create` hit the same dead end. Sharing a curated
|
|
skill with a team is what sharing is *for*.
|
|
- Hints and timestamps are readable now. `--ink-faint` failed the accessibility
|
|
contrast minimum in **both** themes — 3.85:1 in Moria, 3.19:1 in Shire, where
|
|
4.5:1 is the bar — so the smallest text on every screen was the hardest to
|
|
read.
|
|
- The suite runs on **Python 3.11 and 3.12** as well as 3.14. It had only ever
|
|
run on 3.14, while the Docker image ships 3.12 and the packaging claimed 3.11
|
|
— so the one interpreter most people would actually run was the one nothing
|
|
had tested.
|
|
- A `docs/notes/release-checklist.md` for the half of testing a machine cannot
|
|
do: a real endpoint, a real machine, real hardware, a real pair of eyes.
|
|
|
|
## 0.9.12
|
|
|
|
**The security pass.** Six findings, all fixed. None is reachable by simply
|
|
visiting the site; every one of them is a boundary that was supposed to hold
|
|
and did not.
|
|
|
|
- Fixed: **a helper could write files and run programs on the remote machine,
|
|
unattended, in a mode that promises to change nothing.** A subagent is pinned
|
|
to a fixed list of read-only commands — and `find` was on it. `find -fprintf`
|
|
writes a file, `find -exec` runs a program, `find -delete` removes one, and
|
|
none of them needs a character the shell-metacharacter guard refuses. A page
|
|
the model had just read could have asked for a helper and got an SSH key
|
|
written into `authorized_keys`. Those flags are refused outright now, whatever
|
|
list a command is on.
|
|
- Fixed: **an SSH connection could be pointed at `0.0.0.0` and reach the machine
|
|
LLeMbas runs on**, with the "may a connection point here" setting still
|
|
reading *off*. Every other spelling was caught; that one is neither a real
|
|
destination nor a refused one, and connecting to it goes to localhost.
|
|
- Fixed, twice, in the update helper — the one place this deliberately crosses a
|
|
privilege boundary: **root ran a script the unprivileged service account
|
|
owns**, and **root sourced a file that account can replace**. Either turns a
|
|
compromise of the web application into root on the host, which is exactly what
|
|
the unprivileged split exists to prevent. The first also meant control of the
|
|
branch was control of root, with no compromise needed at all.
|
|
**If you installed the update helper before this, re-run the installer** —
|
|
the old wiring stays until you do, and the update script now says so loudly
|
|
when it notices.
|
|
- Fixed: **browser notification endpoints skipped the guard that stops the
|
|
server being aimed at your own network.** It was the only outbound request in
|
|
the codebase not going through it.
|
|
- Fixed: **a chat could be put in another account's folder**, and a folder hands
|
|
its system prompt to the chats inside it — so that read a setting across an
|
|
ownership boundary through a field that looks like a tag.
|
|
- Fixed: a `"` typed into the share panel's search box silently stopped every
|
|
checkbox in the panel from doing anything.
|
|
- Fixed: **re-running the installer moved the update channel to `stable`** even
|
|
on a host following `edge`. The channel lives in two places — the environment
|
|
file the page reads and the systemd unit the button obeys — and a re-run kept
|
|
the first while rewriting the second, so an install for some unrelated reason
|
|
left the page naming one channel and the button deploying another. It now
|
|
defaults to what the host already follows.
|
|
|
|
## 0.9.11
|
|
|
|
- The Updates page no longer runs the **Check the remote** button flush against
|
|
the version and commit above it, where the two read as one block.
|
|
|
|
## 0.9.10
|
|
|
|
**The second audit pass: screens that were harder to use than they needed to
|
|
be.** Checked by rendering them in a real browser and measuring, not by reading
|
|
the CSS.
|
|
|
|
- Fixed: **the Prompts admin page put its reference material first.** The
|
|
Variables legend and the Preview run to a screen each and sat above the tabs,
|
|
so the editor — the thing the page is for — started two screens down and every
|
|
tab switch had to move the whole page to be any use. On a short tab it could
|
|
not move far enough and left the panel stranded above a screenful of nothing.
|
|
The editor comes first now, the reference after, and the tab bar stays put:
|
|
measured, it moved 385→642px between tabs before and does not move at all now.
|
|
The tab bar also sticks to the top, so a long panel does not scroll it away.
|
|
- Fixed: **custom themes were three fixed slots.** A fresh instance opened on
|
|
fifty-seven empty colour boxes under three identical headings, and a fourth
|
|
theme could not be made at all. Now: one block per theme you have, plus one
|
|
blank to add the next, with the colours behind a disclosure — so a theme is a
|
|
name and a starting point until you ask for more. Up to twelve. The page is
|
|
half the height it was.
|
|
- Fixed: **deleting a chat left every file it held on disk.** The rows went —
|
|
the message, the attachments, the generated images — and the files they named
|
|
stayed, with nothing that would ever look at them again. Four of the five ways
|
|
a chat can end had this: the delete button, a schedule's task chat, a helper's
|
|
hidden chat, and deleting an account. There is one function that deletes a
|
|
chat now, and it removes the files first.
|
|
- Fixed, and it is what made the above invisible: **a file attached before the
|
|
chat existed never learned which chat it belonged to.** Anything picked on the
|
|
new-chat screen kept an empty `chat_id` for the rest of its life. Six things
|
|
filter on that, so for those files the model was not told they were attached,
|
|
the canvas would not open them, and the cleanup could not find them.
|
|
- **Folders can be nested, which the README has always claimed.** The route has
|
|
handled it since folders existed — cycle guard, depth limit — and the sidebar
|
|
has always drawn a tree; there was simply no control that could ask for it.
|
|
Moving a folder also respects the depth limit now, which only creating one did.
|
|
- The Proxmox container installs the **update helper by default**. A container
|
|
made thirty seconds ago to run one thing is not the shared host the plain
|
|
installer has to be careful about, and an appliance you cannot update without
|
|
a shell is one nobody updates. `INSTALL_UPDATE_HELPER=0` opts out. Docker
|
|
deliberately has no equivalent: updating a container is pulling an image, and
|
|
a helper inside one would need the Docker socket, which is root on the host.
|
|
- The starting points on the new-chat screen are four new ones, aimed at
|
|
somebody who has just stood an instance up and wants to know what is behind
|
|
it. Only a fresh install gets them; an instance that has already seeded keeps
|
|
whatever its administrator has made of the list.
|
|
- `README.md` describes what this actually is again — schedules, reports,
|
|
helpers, image generation, semantic search, quotas, sharing, branding and the
|
|
updates page were all missing, and two things listed as *planned* had shipped.
|
|
It gained sections on Docker, the Proxmox container and updating.
|
|
|
|
## 0.9.9
|
|
|
|
**The first of five audit passes before 1.0.0** — everything that landed between
|
|
0.8.1 and 0.9.8 read as a whole rather than one feature at a time. This one is
|
|
the main logic, the harness, and every instruction a model is given.
|
|
|
|
- Fixed: **every model was told the time in a zone with no name.** On any
|
|
account that had not chosen a timezone — which is the default state of every
|
|
account — the date line shipped as "Times the person gives you are in
|
|
unless they say otherwise", on every request. The code claimed in two places
|
|
that the line disappeared instead. It never had.
|
|
- Fixed: **the prompt preview could not show most of what it previews.** Eleven
|
|
fragments are gated on things that only exist once there is a real chat, and
|
|
the preview has none — so the whole agent surface, both scheduling fragments
|
|
and the helper warning were missing from it whatever you ticked. Editing
|
|
`tool.agent` and pressing preview showed a system message without `tool.agent`
|
|
in it, and nothing said so. Two new controls come with the fix: what kind of
|
|
chat to preview as, and which agent mode.
|
|
- Fixed: **a model in Plan mode was told to use a tool it did not have.**
|
|
`plan_update` is withdrawn in that mode in favour of `plan_submit`, but its
|
|
guidance appeared whenever a plan existed — directly under the line saying
|
|
anything not in your tool list does not exist.
|
|
- Fixed: **reading one knowledge document could fill the whole context window.**
|
|
Every other reader caps what it returns and says so; this one returned the
|
|
document whole, and its description said "in full", so it did exactly what it
|
|
claimed. A long PDF is now cut at 40,000 characters with the model told.
|
|
- Fixed: **the guidance about helpers on a machine was wrong in both
|
|
directions.** It denied that a helper can write files, which is a documented
|
|
option of the tool beside it, and it named seven of the twenty-three commands
|
|
a helper may run — so a model avoided commands it was allowed to use. Both are
|
|
now checked against the real list and the real schema by tests, because prose
|
|
and a constant drift the moment one is edited alone.
|
|
- The tool description for delegating no longer claims a helper gets "the same
|
|
tools". It gets deliberately fewer, and sizing a task against the wrong set is
|
|
how a whole phase gets planned around something that will refuse it.
|
|
|
|
- The Updates page notices when the update helper on a host was installed for a
|
|
**different channel** than the page follows. It is declared in two places —
|
|
`lembas.env` and the systemd unit — and only the installer writes both, so
|
|
editing one by hand would have left the button deploying something other than
|
|
what the page named, with nothing anywhere saying so.
|
|
- Fixed: release notes from a **signed** tag rendered the signature block.
|
|
`_notes_for` stripped the PGP header only, and which header appears depends on
|
|
`gpg.format` — this repository signs with SSH.
|
|
- A `CHANGELOG.md`, kept from now on rather than assembled at release time.
|
|
|
|
## 0.9.8
|
|
|
|
**Updates follow a channel, not a commit.** `stable` tracks the newest `vX.Y.Z`
|
|
tag; `edge` tracks the branch tip. A branch tip is not a release — following one
|
|
means deploying whatever was pushed five minutes ago — so stable is the default
|
|
for anybody who is not the person writing it.
|
|
|
|
- The Updates page shows a **version** rather than a commit sha: `1.0.0` at a
|
|
tag, `1.0.0-7-gd4f56d` seven commits past one, and a bare sha only before the
|
|
first release exists.
|
|
- Release notes come out of the **annotated tag itself**, so no forge API is
|
|
involved anywhere. That matters: the Gitea API this was checked against
|
|
returns a 500 from a server-side panic on exactly the releases endpoint.
|
|
- A tag with a suffix (`v1.1.0-rc1`) is deliberately not a release — git's
|
|
version sort ranks it *above* `v1.1.0`, so accepting one would step a stable
|
|
host onto a candidate.
|
|
- Fixed: `deploy/update.sh` stopped silently after `== fetching ==` on any host
|
|
with no release tags — which was every host. Fetched, not reset, not
|
|
restarted, and no error printed.
|
|
- Fixed: `install.sh` now refuses an `ssh://` repository URL up front instead of
|
|
letting the clone fail as a service user with no key.
|
|
|
|
## 0.9.7
|
|
|
|
**Packaging, and updating without a shell.**
|
|
|
|
- `/admin/updates`: what is running, what is available, and what changed between.
|
|
A button applies it — answered by an **opt-in** systemd helper, because the
|
|
service runs unprivileged and a web application that can restart its own
|
|
service is one whose worst day is much worse. Without the helper the page says
|
|
so and prints the command.
|
|
- `Dockerfile` and `docker-compose.yml`. No secret key, no data and no `.git`
|
|
baked in; loopback only; a TLS proxy expected in front, because a service
|
|
worker and a microphone both require HTTPS or localhost.
|
|
- `deploy/lxc-install.sh` creates an unprivileged Proxmox container and runs the
|
|
existing installer inside it.
|
|
- `/healthz`, which opens the database rather than only proving the socket is
|
|
listening.
|
|
|
|
## 0.9.6
|
|
|
|
**Permissions, quotas and sharing.**
|
|
|
|
- **"What can this account actually do?"** answered on screen, naming *where*
|
|
each permission came from — admin, the baseline, or a group.
|
|
- Users and groups are list-plus-detail, and membership is edited from **one**
|
|
side. It was on both, and a save from either overwrote what the other showed.
|
|
- Reading and writing split for notes, memory and skills.
|
|
- **Quotas on a group** — monthly tokens, concurrent replies, agent wall clock,
|
|
images a day, helpers a reply. Resolved by maximum across a person's groups,
|
|
with zero meaning *no limit* and winning outright.
|
|
- Fixed: **deleting a group or an account left every share naming it behind.**
|
|
`forget_principal` had existed since shares did and was called by nobody.
|
|
- Fixed: `library.share` defaulted to off, so sharing shipped documented as done
|
|
and unreachable — the panel only renders for somebody who holds it.
|
|
- The share panel is its own action with a search box. It used to be checkboxes
|
|
inside the resource's save form, listing every account on the instance, and a
|
|
tick only took effect if you also saved the resource.
|
|
- Reports are shareable, and every listing has a **Shared with me** filter.
|
|
|
|
## 0.9.5
|
|
|
|
**Extraction settings, embeddings, and hybrid search.**
|
|
|
|
- `/admin/extraction`: upload size, image edge, JPEG quality, PDF pages,
|
|
extracted characters, orphan age, extra text extensions.
|
|
- An **embedding model** can be chosen from models flagged for it. Library search
|
|
then fuses keyword and semantic ranking, so *"how do I get paid"* finds a
|
|
document that says *"invoicing"*.
|
|
- **Choosing none is not a degraded mode**: no rows written, no requests made,
|
|
and byte-for-byte the keyword search that was always there.
|
|
- Vectors carry their model and width, and a mismatch is skipped rather than
|
|
scored — comparing two embedding spaces produces a confident wrong answer.
|
|
- Indexing happens in the background as records are written, with a rebuild
|
|
button for everything that already existed.
|
|
|
|
## 0.9.4
|
|
|
|
**An instance can be somebody else's.**
|
|
|
|
- Name, tagline, logo, favicon and launcher icons derived from the logo.
|
|
- The Middle-earth wording is editable data. Leaving a box alone does not freeze
|
|
it, so a later release can still improve the default.
|
|
- **Custom themes** as a set of colours rather than a stylesheet, inheriting
|
|
whichever built-in they start from.
|
|
- Global CSS overrides, served as `/branding.css`.
|
|
|
|
## 0.9.3
|
|
|
|
**Subagents.** A reply can hand a self-contained piece of work to a helper that
|
|
runs on its own and reports back — several at once, so research fans out instead
|
|
of queueing.
|
|
|
|
- A helper cannot ask questions, cannot send helpers of its own, writes nothing
|
|
unless the call asked and the chat's mode allowed it, and on a machine runs
|
|
only a fixed list of read-only commands — in **every** mode, including Auto.
|
|
- Fixed, and it was live in scheduled runs too: an unattended chat that hit an
|
|
approval built a card nobody could see and sat on it for fifteen minutes.
|
|
|
|
## 0.9.2
|
|
|
|
**Image generation defaults an administrator can actually set** — steps, cfg,
|
|
size, sampler, scheduler, denoise, negative prompt, checkpoint, batch. There were
|
|
none: one hard-coded set from the SD1.5 era, and prose in a box as the only way
|
|
to change it.
|
|
|
|
- The samplers and schedulers ComfyUI had been reporting all along are now the
|
|
pickers; nothing had ever read them.
|
|
- The tool's own schema restates the instance's defaults, instead of telling the
|
|
model "Default 512" beside an instance that draws at 1024.
|
|
|
|
## 0.9.1
|
|
|
|
**Everything that arrives is announced, not only chat replies.** A scheduled run
|
|
that filed a report used to light a dot in a corner and say nothing.
|
|
|
|
- A count in the tab title while you are looking elsewhere.
|
|
- **Web push**, so a schedule firing at seven in the morning reaches a browser
|
|
that is shut. Opt-in per device. It is the one thing here that contacts an
|
|
outside service, and `services/push.py` says so plainly.
|
|
|
|
## 0.9.0
|
|
|
|
**A model can schedule things.** There was no tool for it — asked to "remind me
|
|
every Monday", a model wrote a note and reported that it had scheduled
|
|
something, and every screen agreed with it.
|
|
|
|
- `schedule_create`, `schedule_list`, `schedule_update`, `schedule_cancel`, over
|
|
the same rule normaliser the manual form uses.
|
|
- The reply says the resulting timing back in words, which is the only moment
|
|
anybody can check that Monday was understood as Monday.
|
|
|
|
## 0.8.3
|
|
|
|
**An SSH connection may not point at this machine unless an administrator says
|
|
so.** A profile aimed at `127.0.0.1` walked straight past "nothing runs on the
|
|
LLeMbas host" — through a real login, onto the machine holding the database and
|
|
the encryption key. Three positions: off, one named port, or anywhere.
|
|
|
|
## 0.8.2
|
|
|
|
- Fixed: **opening the canvas before a chat existed swapped the whole site into
|
|
the panel.** `hx-get=""` is not "fetch nothing" — htmx looks for the attribute,
|
|
not the value, so the empty one was a real request for the current document.
|
|
- Fixed: the Canvas and Terminal buttons appeared where they could not work.
|
|
- The bottom edge of the shell is no longer drawn, so the sidebar footer and the
|
|
composer stop meeting a line at two different heights.
|
|
- Admin pages scroll in one container; `/admin/prompts` no longer drops you at
|
|
the bottom of a shorter panel.
|