Files
devplacepy/devplacepy/schemas/listings.py
T
retoorandClaude Sonnet 5 572e022584 Add thread notifications, SEO topic pages, and fix quiz auto-advance
Notifications: a new "thread" type notifies every other commenter on a
post whenever anyone comments on it, disregarding reply hierarchy -
excluding the actor and whoever already got a comment/reply
notification for that same event, so no one is double-notified.
Implemented via a background-deferred fan-out mirroring the existing
mention-notification pattern.

SEO: discussion_forum_posting() now embeds up to 20 of a post's
comments as nested schema.org Comment entities (not just an aggregate
count), and a new /topics hub plus /topics/{topic} pages give the
feed's topic filter real, independently crawlable/indexable URLs -
/feed?topic=X was never indexable since its canonical strips the
query string back to bare /feed. Both are wired end to end (schemas,
Devii actions, docs API, sitemap, locustfile load-test coverage).

Quiz player: the auto-advance to the next question used to hide the
just-answered slide in the same tick as rendering the grade, so on
any multi-question quiz the Correct/Not correct feedback was never
actually visible before the view moved on. Delayed via setTimeout,
with the pending timer cleared on manual navigation and on
disconnect so it can't race or fire on a removed component.

Also includes other local changes already in progress in this
working tree before this session (messaging, push delivery,
deepsearch jobs, game economy, quiz builder) - verified by the full
suite passing (3467 tests) but not authored or individually reviewed
in this session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VL9Xn57W5UR3HZbbuuzxdK
2026-09-03 08:47:57 +02:00

274 lines
7.1 KiB
Python

# retoor <retoor@molodetz.nl>
from __future__ import annotations
from typing import Any, Optional
from devplacepy.schemas.base import _Out
from devplacepy.schemas.battles import WarOut
from devplacepy.schemas.content import (
AttachmentOut,
CommentItemOut,
GistOut,
MessageOut,
NewsOut,
NotificationOut,
PollOut,
PostOut,
ProjectLinkOut,
ProjectOut,
ReactionsOut,
UserOut,
)
class FeedItemOut(_Out):
post: PostOut
maturity: Optional[str] = None
author: Optional[UserOut] = None
time_ago: Optional[str] = None
my_vote: int = 0
comment_count: int = 0
attachments: list[AttachmentOut] = []
recent_comments: list[CommentItemOut] = []
reactions: ReactionsOut = ReactionsOut()
bookmarked: bool = False
poll: Optional[PollOut] = None
war: Optional[WarOut] = None
project_link: Optional[ProjectLinkOut] = None
class GistItemOut(_Out):
gist: GistOut
maturity: Optional[str] = None
author: Optional[UserOut] = None
time_ago: Optional[str] = None
my_vote: int = 0
comment_count: int = 0
recent_comments: list[CommentItemOut] = []
class ProjectListItemOut(ProjectOut):
author_name: Optional[str] = None
my_vote: int = 0
recent_comments: list[CommentItemOut] = []
class NewsListItemOut(_Out):
article: NewsOut
time_ago: Optional[str] = None
image_url: Optional[str] = None
grade: Optional[int] = None
featured: Optional[int] = None
recent_comments: list[CommentItemOut] = []
class ConversationOut(_Out):
other_user: Optional[UserOut] = None
last_message: Optional[str] = None
last_message_at: Optional[str] = None
unread: bool = False
class MessageItemOut(_Out):
message: MessageOut
sender: Optional[UserOut] = None
is_mine: bool = False
time_ago: Optional[str] = None
attachments: list[AttachmentOut] = []
grouped: bool = False
class NotificationItemOut(_Out):
notification: NotificationOut
actor: Optional[UserOut] = None
time_ago: Optional[str] = None
class NotificationGroupOut(_Out):
label: Optional[str] = None
entries: list[NotificationItemOut] = []
class LeaderboardEntryOut(_Out):
uid: Optional[str] = None
username: Optional[str] = None
avatar_seed: Optional[str] = None
stars: Optional[int] = None
level: Optional[int] = None
rank: Optional[int] = None
class SavedItemOut(_Out):
target_type: Optional[str] = None
target_uid: Optional[str] = None
type_label: Optional[str] = None
title: Optional[str] = None
url: Optional[str] = None
time_ago: Optional[str] = None
class TopicOut(_Out):
posts: list[FeedItemOut] = []
topic: str = ""
topic_label: str = ""
next_cursor: Optional[str] = None
class TopicSummaryOut(_Out):
key: str = ""
label: str = ""
post_count: int = 0
class TopicsHubOut(_Out):
topics: list[TopicSummaryOut] = []
class FeedOut(_Out):
posts: list[FeedItemOut] = []
current_tab: Optional[str] = None
current_topic: Optional[str] = None
search: Optional[str] = None
next_cursor: Optional[str] = None
total_members: Optional[int] = None
posts_today: Optional[int] = None
total_projects: Optional[int] = None
total_gists: Optional[int] = None
top_authors: list[UserOut] = []
daily_topic: Optional[Any] = None
online_users: list[UserOut] = []
class PostDetailOut(_Out):
maturity: Optional[str] = None
post: PostOut
author: Optional[UserOut] = None
is_owner: bool = False
star_count: int = 0
my_vote: int = 0
time_ago: Optional[str] = None
comments: list[CommentItemOut] = []
attachments: list[AttachmentOut] = []
reactions: ReactionsOut = ReactionsOut()
bookmarked: bool = False
poll: Optional[PollOut] = None
war: Optional[WarOut] = None
comment_count: Optional[int] = None
related_posts: list[FeedItemOut] = []
topics: list[str] = []
project_link: Optional[ProjectLinkOut] = None
class ProjectsOut(_Out):
projects: list[ProjectListItemOut] = []
current_tab: Optional[str] = None
search: Optional[str] = None
project_type: Optional[str] = None
total_count: Optional[int] = None
next_cursor: Optional[str] = None
total_members: Optional[int] = None
top_authors: list[UserOut] = []
class ProjectDetailOut(_Out):
maturity: Optional[str] = None
project: ProjectOut
author: Optional[UserOut] = None
is_owner: bool = False
star_count: int = 0
my_vote: int = 0
time_ago: Optional[str] = None
comments: list[CommentItemOut] = []
attachments: list[AttachmentOut] = []
reactions: ReactionsOut = ReactionsOut()
bookmarked: bool = False
platforms: Optional[Any] = None
is_private: bool = False
read_only: bool = False
viewer_can_containers: bool = False
viewer_can_workspace: bool = False
workspace_editor_url: Optional[str] = None
workspace_editor_mode: Optional[str] = None
workspace_editor_width: Optional[int] = None
workspace_editor_height: Optional[int] = None
forked_from: Optional[dict] = None
fork_count: int = 0
file_count: int = 0
comment_count: int = 0
devlog_posts: list[FeedItemOut] = []
devlog_next_cursor: Optional[str] = None
devlog_count: int = 0
class GistsOut(_Out):
gists: list[GistItemOut] = []
total_count: Optional[int] = None
next_cursor: Optional[str] = None
current_language: Optional[str] = None
search: Optional[str] = None
languages: list[tuple[str, str]] = []
gist_language_codes: list[str] = []
class GistDetailOut(_Out):
maturity: Optional[str] = None
gist: GistOut
author: Optional[UserOut] = None
is_owner: bool = False
star_count: int = 0
my_vote: int = 0
time_ago: Optional[str] = None
comments: list[CommentItemOut] = []
attachments: list[AttachmentOut] = []
reactions: ReactionsOut = ReactionsOut()
bookmarked: bool = False
class NewsListOut(_Out):
articles: list[NewsListItemOut] = []
next_cursor: Optional[str] = None
class NewsDetailOut(_Out):
maturity: Optional[str] = None
article: NewsOut
canonical_slug: Optional[str] = None
image_url: Optional[str] = None
grade: Optional[int] = None
time_ago: Optional[str] = None
comments: list[CommentItemOut] = []
bookmarked: bool = False
class MessagesOut(_Out):
conversations: list[ConversationOut] = []
messages: list[MessageItemOut] = []
other_user: Optional[UserOut] = None
current_conversation: Optional[str] = None
search: Optional[str] = None
other_online: bool = False
other_last_seen: Optional[str] = None
class NotificationsOut(_Out):
notification_groups: list[NotificationGroupOut] = []
next_cursor: Optional[str] = None
class LeaderboardOut(_Out):
entries: list[LeaderboardEntryOut] = []
user_rank: Optional[int] = None
total_members: Optional[int] = None
posts_today: Optional[int] = None
total_projects: Optional[int] = None
total_gists: Optional[int] = None
top_authors: list[UserOut] = []
featured_news: list[NewsOut] = []
class SavedOut(_Out):
items: list[SavedItemOut] = []
next_cursor: Optional[str] = None