Archived chats no longer show inside folders
The unfiled list has filtered archived chats since archiving existed (api/pages.py). The folder branch went through the ORM relationship, which filters nothing, so an archived chat kept appearing as long as it was filed -- and the "Empty" check read the same unfiltered list, so a folder holding only archived chats would have claimed to be empty while listing them. Fixed on the model rather than in the template, as `Folder.visible_chats`. The loop and the empty check now cannot disagree, because there is one list and the template binds it once. Ordering matches the unfiled list: pinned first, then most recently touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,24 @@ class Folder(UUIDPrimaryKey, Timestamps, Base):
|
|||||||
parent: Mapped[Folder | None] = relationship(back_populates="children", remote_side="Folder.id")
|
parent: Mapped[Folder | None] = relationship(back_populates="children", remote_side="Folder.id")
|
||||||
chats: Mapped[list[Chat]] = relationship(back_populates="folder")
|
chats: Mapped[list[Chat]] = relationship(back_populates="folder")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def visible_chats(self) -> list[Chat]:
|
||||||
|
"""The chats in this folder that belong in the sidebar.
|
||||||
|
|
||||||
|
The relationship itself stays unfiltered -- back-population needs every
|
||||||
|
row -- so the listing rule lives here rather than in the template, where
|
||||||
|
the loop and the "Empty" check would have to agree by hand and already
|
||||||
|
did not: archived chats have been showing inside folders since folders
|
||||||
|
existed. The unfiled list has always filtered them (api/pages.py); the
|
||||||
|
folder branch went through the relationship and filtered nothing.
|
||||||
|
|
||||||
|
Ordered like the unfiled list: pinned first, then most recently touched.
|
||||||
|
"""
|
||||||
|
kept = [chat for chat in self.chats if not chat.archived]
|
||||||
|
kept.sort(key=lambda chat: chat.updated_at, reverse=True)
|
||||||
|
kept.sort(key=lambda chat: not chat.pinned)
|
||||||
|
return kept
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"<Folder {self.name}>"
|
return f"<Folder {self.name}>"
|
||||||
|
|
||||||
|
|||||||
@@ -30,17 +30,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="folder__contents" x-show="open" x-cloak>
|
<div class="folder__contents" x-show="open" x-cloak>
|
||||||
|
{# Bound once: the loop and the "Empty" check must be looking at the same
|
||||||
|
list, or a folder holding only archived chats claims to be empty while
|
||||||
|
showing them. #}
|
||||||
|
{% set listed = folder.visible_chats %}
|
||||||
|
|
||||||
{% for child in folder.children %}
|
{% for child in folder.children %}
|
||||||
{% with folder = child %}
|
{% with folder = child %}
|
||||||
{% include "partials/_folder.html" %}
|
{% include "partials/_folder.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% for chat_item in folder.chats %}
|
{% for chat_item in listed %}
|
||||||
{% include "partials/_chat_link.html" %}
|
{% include "partials/_chat_link.html" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if not folder.children and not folder.chats %}
|
{% if not folder.children and not listed %}
|
||||||
<p class="nav-empty">Empty</p>
|
<p class="nav-empty">Empty</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -314,6 +314,28 @@ def test_deleting_a_folder_keeps_the_chats_inside_it(client: TestClient, db, reg
|
|||||||
assert chat.folder_id is None
|
assert chat.folder_id is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_archived_chat_inside_a_folder_is_not_listed(
|
||||||
|
client: TestClient, db, registered, make_chat
|
||||||
|
):
|
||||||
|
"""Regression: the unfiled list has always filtered archived chats, but the
|
||||||
|
folder branch went through the ORM relationship and filtered nothing, so an
|
||||||
|
archived chat kept showing as long as it was filed."""
|
||||||
|
_add_connection(db)
|
||||||
|
client.post("/api/folders", data={"name": "Quests"})
|
||||||
|
folder = db.scalar(select(Folder))
|
||||||
|
|
||||||
|
chat_id = make_chat()
|
||||||
|
client.patch(f"/api/chats/{chat_id}", data={"folder_id": folder.id, "title": "Mount Doom"})
|
||||||
|
chat = db.get(Chat, chat_id)
|
||||||
|
chat.archived = True
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
page = client.get("/chat").text
|
||||||
|
assert "Mount Doom" not in page
|
||||||
|
# And the folder must say so, rather than claiming to hold something.
|
||||||
|
assert "Empty" in page
|
||||||
|
|
||||||
|
|
||||||
def test_a_folder_cannot_be_moved_inside_itself(client: TestClient, db, registered):
|
def test_a_folder_cannot_be_moved_inside_itself(client: TestClient, db, registered):
|
||||||
client.post("/api/folders", data={"name": "Outer"})
|
client.post("/api/folders", data={"name": "Outer"})
|
||||||
folder = db.scalar(select(Folder))
|
folder = db.scalar(select(Folder))
|
||||||
|
|||||||
Reference in New Issue
Block a user