Commit Graph
356 Commits
Author SHA1 Message Date
retoorandClaude Sonnet 5 7d32ef17dd Untrack prompt.md and prompt2.md
These are personal scratch notes, not repository content - they were
committed unintentionally by an earlier automated step. Removed from
tracking; the files remain on disk.

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
retoorandClaude Sonnet 5 57087536e5 Attribute Devii AI spend to its invoking action, fix quiz question-at-a-time review, DB API/isslop result routes, workspace docs, and drop redundant docstrings
- Route Devii-driven AI gateway cost to the action/tool that triggered
  it instead of a blanket "internal" bucket, so per-feature AI spend
  is attributable.
- Fix the quiz attempt review to show one previously-answered question
  at a time instead of all of them at once, and stop a quiz endpoint
  linked from the quiz flow from responding with raw JSON.
- Add DB API async query result route and AI Usage Analyzer annotated
  source/media routes, with traversal-safe uid/path handling and
  matching tests.
- Add Code Farm action audit logging (plant/harvest/buy-plot/upgrade/
  fertilize) and related admin workspace/services/trash/gateway route
  and doc touch-ups.
- Drop redundant docstrings from access_tokens.py per the no-comments
  convention.

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
retoorandClaude Sonnet 5 9d7b3db314 Fix fanout gaps: statistics JSON 500, schema drops, undocumented admin routes
- Fix StatisticsOut to match the real admin_statistics page context
  (active_tab/tabs/window_hours/initial), which was raising a
  ValidationError and 500ing GET /admin/statistics with
  Accept: application/json.
- Add missing *Out fields dropped from JSON responses: IsslopSourceOut
  (source_lines, marked_lines, focus_line, report_url), IsslopReportOut
  (report_url, badge_url, events_url, topic), IssuesOut
  (viewer_is_admin), GameStateOut/GameFarmViewOut (game_error),
  QuizAttemptPageOut (answer_max_chars, quiz_error), QuizBuilderOut
  (quiz_error).
- Convert GET /admin/issues/planning to respond() with a new
  AdminIssuesPlanningOut schema so it serves JSON like every sibling
  admin dashboard, and document it in docs_api.
- Document previously-undocumented admin endpoint families in
  docs_api: services page routes, gateway provider/model CRUD, admin
  workspaces (11 routes), and trash list/restore/purge.
- Correct stale references to services/devii/actions/catalog.py as a
  single file; it is the actions/catalog/ package.

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
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
retoorandClaude Sonnet 5 afb4799869 Fix mobile chat composer glitches and add date separators/reconnect banner
Composer focus/blur no longer fights the on-screen keyboard: tapping
Send blurred the textarea first, which unconditionally zeroed the
visualViewport keyboard-inset compensation before the immediate
refocus could recompute it, so the composer could sit misplaced for
up to 600ms after every mobile send. Blur now only resets the inset
when focus is actually leaving the composer form, and focus
recomputes it immediately instead of waiting on the retry timers.
Emoji picker and @mention wiring, previously only ever applied once
at page load, are now re-run whenever AppChat rebuilds a composer for
a client-side conversation switch, so both survive tapping a
conversation from the list instead of only working after a full page
reload. Viewport meta gains interactive-widget=resizes-content for
native keyboard-aware layout. Chat scroll panes get
overscroll-behavior: contain plus momentum scrolling, matching the
pattern already used elsewhere in the app. The conversation-search
dropdown's literal z-index is replaced with the --z-popover token.

Adds sticky Today/Yesterday/date separators between message groups
(chat/DateSeparators.js, viewer-local-time day boundaries, normalized
on every thread mutation) and a debounced "Reconnecting..." banner
reflecting live WebSocket state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qsdit8UXhbUn9ZnfgjbXqt
2026-09-03 08:47:57 +02:00
retoor 3ca9285646 Update
DevPlace CI / test (push) Failing after 1h41m11s
2026-09-03 04:20:55 +00:00
retoor be4da6dc5c Update 2026-09-03 04:19:58 +00:00
retoor 0036ea2204 Disable parallel coverage data collection 2026-09-03 03:56:05 +00:00
retoor 317a04f1b4 Cleanup
DevPlace CI / test (push) Failing after 1h39m57s
2026-09-03 03:02:23 +00:00
retoor 055c7bcd07 Merge pull request 'Opinion Wars: week-long two-faction battles on posts' (#173) from blindxfish/devplacepy:OpinionWar into master
DevPlace CI / test (push) Failing after 1h23m35s
Reviewed-on: #173
2026-08-22 07:42:14 +02:00
blindxfishandClaude Fable 5 50baf9d6f1 Fix battles join docs param location and the fight-flow ticker assertion
DevPlace CI / test (pull_request) Has been cancelled
The auth matrix probes documented endpoints with their documented form
params; the faction param was declared with location body instead of
form, so the anonymous probe sent an empty body and hit 422
validation before the auth guard. And the first fight always triggers
a lead-change event that outranks the fight event at the top of the
ticker, so the e2e flow now asserts on the ticker as a whole.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 00:05:00 +02:00
blindxfishandClaude Fable 5 80956ce0f4 Add Opinion Wars: week-long two-faction battles attached to posts
A new post attachment type beside polls: the composer gains a Start
Opinion War builder (same disabled-inputs opt-in as the poll builder)
that names exactly two factions; the battle runs for exactly 7 days
from post creation. Members join a side, may defect at any time
(damage already dealt stays with the faction it was dealt to), and
fight once per 24 hours per battle. A fight spends 25 Code Farm coins
and deals deterministic level-weighted damage: 100 + 10 * min(level,
20) HP, so a newcomer deals 110 and a veteran caps at 300 - no
randomness anywhere.

The battle renders on the post card as a CSS pixel-art battlefield
(box-shadow sprites: castles, faction flags, marching soldiers, a
flickering campfire; steps() animation, disabled under reduced motion)
with live HP bars, a countdown, the viewer's faction strip, top
contributors and an event ticker. Live frames ride pub/sub on
public.battle.{uid} via a relay on the service-lock owner, with the
durable opinion_war_events trail (per-war atomic seq) as the source of
truth and a 15s incremental poller as fallback. /battles lists battles
with active/ended/mine filters, search and pagination.

Every mutation is a conditional UPDATE via conditional_update_row: the
fight sequence claims the cooldown first, then spends coins, then lands
the damage, compensating earlier steps on any later refusal so a crash
costs a turn, never coins. Resolution is lazy on read (no cron):
an exactly-once CAS computes the winner in the statement, awards XP
(participation, winner bonus, top damage dealer bonus; draws pay
participation only), emits the result event and notifies fighters. The
OpinionWarService backstop resolves unviewed wars and sends
fight-ready notifications, exactly-once via a marker CAS.

Fan-out: battle notification type, four badges, audit keys
(battle.create/join/switch/fight/resolve), Devii actions (join/fight
confirm-gated), API docs group, docs prose page, sitemap and topnav
entries, REPORTABLE_TARGETS registration, post-delete cascades,
README and nested CLAUDE.md documentation.

Verified with the four-layer procedure: property checks over the full
damage domain, 1200-step stateful fuzz (hp-sum invariant, coins never
negative, resolved totals frozen), and real 8-process races proving
exactly-once semantics for concurrent fights, double-spends across two
wars, resolution XP and double-joins. Persisted tests in
tests/unit/services/opinionwar, tests/api/battles, tests/e2e/battles
and tests/api/posts/create.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 23:30:02 +02:00
retoor 2f26dbb1e7 Update
DevPlace CI / test (push) Failing after 1h42m39s
2026-08-20 00:38:07 +02:00
retoor 516219513a Merge pull request 'Show a post's image on its card, and show it full size' (#172) from blindxfish/devplacepy:PostCardPolish into master
DevPlace CI / test (push) Failing after 1h34m22s
Reviewed-on: #172
2026-08-19 22:11:02 +02:00
blindxfishandClaude Opus 5 8856c38b4d Show a post's image on its card, and show it full size
DevPlace CI / test (pull_request) Has been cancelled
_attachment_display.html iterates a context variable named
`attachments`, so every caller binds it before the include.
_post_card.html was the one caller that did not: it guarded on
item.attachments but included the partial with nothing bound, so the
gallery looped over whatever `attachments` happened to be in the
surrounding page context and rendered empty. Every post with an image
looked image-less on the feed and on profiles, and on a project page -
where project_detail.html sets `attachments` at template scope for the
project's own files - a devlog card would have rendered the project's
files as its own.

With the image actually reaching the card, render a lone one properly:
a gallery holding exactly one item gets a `single` class and takes the
full content column (max-height 480px, object-fit contain, no hover
scale), matching the original DevPlace. That branch serves the stored
original rather than thumbnail_url, because a thumbnail is 200px on its
longest side and stretching it to the column width is visibly blurry.

Animated GIFs needed no change and now have a test proving it: they
never had a thumbnail to flatten, so they already took the original-file
path and simply render larger.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 23:14:26 +02:00
retoor 08d370b020 Merge pull request 'Attach images pasted into the composer, comments and chat' (#171) from blindxfish/devplacepy:PasteImage into master
DevPlace CI / test (push) Failing after 1h31m51s
Reviewed-on: #171
2026-08-17 22:44:45 +02:00
blindxfishandClaude Opus 5 076f55f380 Attach images pasted into the composer, comments and chat
DevPlace CI / test (pull_request) Has been cancelled
Pressing Ctrl+V with a screenshot on the clipboard now attaches it
immediately instead of requiring a trip through the file picker.

The clipboard reader lives in dp-upload behind a new opt-in `paste`
boolean attribute: with it set, the component binds one paste listener
on its closest form and routes the clipboard image files through the
same handleFiles path as the picker and the drop target, so validation,
limits, the terms gate and the hidden attachment_uids field are shared.
A paste carrying plain text is never swallowed.

It is opt-in rather than a form-wide default because a form may hold
several upload buttons - projects.html has cover and logo beside the
attachment one - and a default would attach one pasted screenshot to
all of them.

Set on _attachment_form.html, so every form including it inherits the
behaviour (post composer, post edit, gists, projects, issues,
screenshots), plus _comment_form.html, messages.html and the embed-mode
skeleton AppChat builds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:23:25 +02:00
retoor e0672f896d Merge pull request 'feat: Implement links to the official iOS app in the web app' (#170) from typosaurus/169-implement-links-to-the-official-ios-app-in-the-web-app into master
DevPlace CI / test (push) Failing after 26m50s
Reviewed-on: #170
2026-08-16 06:37:41 +02:00
typosaurus c9802440d7 test(sveta): Write e2e tests for the iOS app badges
DevPlace CI / test (pull_request) Failing after 28m47s
Outcome: done
Changed: tests/e2e/iosapp.py:1-94 (new file)
Verified by: python -m pytest tests/e2e/iosapp.py -q -> 5 passed (3 consecutive clean runs, incl. verify()); full make test -> 3348 passed, 1 skipped, 11 failed (9 tests/api/projects/workspace.py FileNotFoundError 'docker' = missing docker binary in container, pre-existing per sibling run; 1 tests/e2e/game/farm.py steal flake, passes standalone; 1 was this file's pre-fix settings-propagation race, fixed)
Findings: tests/e2e/iosapp.py locks the badge behaviour: footer badge visible on landing (.landing-footer) and /feed (.site-footer), guest topnav badge next to Login/Sign Up, mobile-panel badge after opening the hamburger, every href equals the configured ios_app_url with target=_blank rel=noopener noreferrer, and all badges disappear when ios_app_url is cleared (restored in try/finally).
Settings-flip e2e tests must sleep CACHE_VERSION_PROPAGATION_SECONDS (1.5s) after set_setting before navigating: the server caches the cache_state version for 1s (core.py:56), and the first iosapp run hit that race (badge absent on a stale render).
On a fresh DB the operational_defaults block incl. ios_app_url is never seeded because it is gated on the pre-init db.tables snapshot (schema.py:34 vs :1705), so the badge renders only after an admin sets ios_app_url.
Open: re-run make test on a docker-capable host to clear the 9 workspace.py environment failures; decide the fresh-DB ios_app_url seeding gap (PM/implementation node); farm s

Typosaurus-Run: 2910ed1ba2604be09c649141af1baf44
Typosaurus-Node: e8e69f65a5fa40c2bf6a8b0206cfb2d4
Typosaurus-Agent: @sveta
Refs: #169
2026-08-16 03:59:11 +00:00
typosaurus 15f04e0d13 feat(nadia): Implement iOS app badges in footer, topnav, and mobile menu
Outcome: done
Changed: devplacepy/templates/base.html:126-132,189-195; devplacepy/templates/_footer_links.html:12-19; devplacepy/templates/admin_settings.html:33-38; devplacepy/routers/admin/settings.py:19; devplacepy/templating.py:197-199,218; devplacepy/static/css/base.css:720-731,785-793,1151-1161,1210-1212; devplacepy/database/CLAUDE.md:224,228
Verified by: import gate clean; py_compile clean; ruff/pyflakes no new findings; TestClient smoke (badges present with href=ios_app_url, target=_blank rel=noopener noreferrer, static_url-versioned src on /, /feed, /leaderboard; absent when setting empty; admin clear via /admin/settings POST verified); make test-fast: 2659 passed, 1 skipped, 10 failed - all environmental (9 missing docker binary, 1 missing libcairo.so.2), none caused by this change; Playwright e2e inapplicable (chromium cannot launch: libglib-2.0.so.0 missing)
Findings: Badges render only when ios_app_url site setting is non-empty and are editable/clearable via AdminSettingsForm (CLEARABLE_SETTINGS). Researched URL https://apps.apple.com/app/devplace/id6797215143 (trackId 6797215143, bundleId wok.DevPlaceIOS) confirmed via iTunes Lookup API and seeded in schema.py operational_defaults by sibling commit. Topnav badge hides at <=1024px where the mobile panel badge takes over, keeping the 360px layout overflow-free. Badge SVG is Apple's official white variant vendored at devplacepy/static/app-store-badge.svg with retoor header. Apple badge marketing guidance (40px min

Typosaurus-Run: 2910ed1ba2604be09c649141af1baf44
Typosaurus-Node: 2817fd803023406699936e88b76ee632
Typosaurus-Agent: @nadia
Refs: #169
2026-08-16 03:59:11 +00:00
typosaurus 551d540bc7 feat(zoya): Review the research findings against the node acceptance criteria
Outcome: done
Changed: none
Verified by: not verified - review-only node; repo cross-checks done (devplacepy/templates/docs/contact.html:5, devplacepy/templates/admin_settings.html:192, devplacepy/database/schema.py:1746, devplacepy/static/app-store-badge.svg)
Findings: Verdict: accept with findings - every parent acceptance criterion is addressed by sourced, tiered findings; none of the notes below is blocking.
The verified URL is already consumed in-flight: uncommitted ios_app_url default at devplacepy/database/schema.py:1746 plus AdminSettingsForm field (devplacepy/models.py) and ios_app_url() Jinja global (devplacepy/templating.py); later nodes must not re-introduce it and must review that diff separately.
Apple's official badge SVG is vendored at devplacepy/static/app-store-badge.svg (title Download_on_the_App_Store_Badge_US-UK_RGB_blk_4SVG_092917, 119.66407x40), matching leaf-2's source recommendation; it sits at the static root rather than the leaf's suggested static/vendors/, a consistency-review call, and the leaf's own commit declared "Changed: none" despite adding it.
"Confirmed live" evidence is DuckDuckGo-indexed storefront snapshots (/fr/, /th/, /il/) via the sanctioned aggregator rsearch.app.molodetz.nl, direct egress to apps.apple.com being blocked and disclosed; corroborated by github.com/WilhelmOks/DevPlaceIOS, so a manual click-through before release is advised.
Apple's badge terms (min 40px onscreen height, one badge per layout) vs the ticket's "small badg

Typosaurus-Run: 2910ed1ba2604be09c649141af1baf44
Typosaurus-Node: 0c6857c89c9448589c0d0141ee774948
Typosaurus-Agent: @zoya
Refs: #169
2026-08-16 03:59:11 +00:00
typosaurus fd409ceea7 feat(vera): Verify the official DevPlace iOS app listing on the App Store
Outcome: done
Changed: none
Verified by: not verified (no files changed; evidence cross-checked from three live storefront variants of the same listing id)
Findings: Official DevPlace iOS app verified live on the App Store, retrieved 16/08/2026: name "DevPlace" (subtitle "The Developer Social Network"), developer "Wilhelm Oks", id 6797215143, canonical URL https://apps.apple.com/app/devplace/id6797215143 (confirmed live via /fr/, /th/, /il/ storefront variants).
Listing confirmed as the official DevPlace client: feature list and description match the DevPlace web app and point users to "DevPlace on the web"; cross-checked against devplacepy/templates/docs/contact.html:5 and devplacepy/templates/admin_settings.html:192, which state DevPlace declares trader info in the app stores that carry a DevPlace client.
Publisher on the listing is the personal name "Wilhelm Oks" (Provider; Copyright © 2026 Wilhelm Oks); the mapping to repo identity retoor@molodetz.nl is an assumption, not provable from inside the repo.
No App Store URL exists anywhere in the repo (grep 16/08/2026); the verified URL must be introduced as new, e.g. as a shared constant.
Direct egress to apps.apple.com and itunes.apple.com is blocked in this run; listing content was retrieved via the sanctioned aggregator rsearch.app.molodetz.nl (timestamps 2026-08-16T01:44-01:45Z).
Corroboration: github.com/WilhelmOks/DevPlaceIOS ("A native iOS app for DevPlace") matches the App Store developer name.
Open: badge graphic ass

Typosaurus-Run: 2910ed1ba2604be09c649141af1baf44
Typosaurus-Node: 42c9a7671c2e4a0e960c2adf44323954
Typosaurus-Agent: @vera
Refs: #169
2026-08-16 03:59:11 +00:00
retoor d7d489681a Update
DevPlace CI / test (push) Failing after 1h32m13s
2026-08-16 05:00:52 +02:00
retoor 62910b0726 Update
DevPlace CI / test (push) Failing after 29m48s
2026-08-16 04:01:17 +02:00
retoorandClaude Sonnet 5 8db0efff29 Fix Devii open-trigger race and close two flaky e2e waits
DevPlace CI / test (push) Failing after 1h30m15s
DeviiTerminal bound [data-devii-open] click listeners only after the
async /devii/session fetch resolved, silently dropping early clicks.
Switch to a single delegated document listener bound in the
constructor, matching the ModalManager/dp-lightbox pattern.

The steal-confirm and comment-vote e2e tests asserted DOM state right
after a click with no wait for the triggering POST to land, racing the
server under CI load. Wrap those clicks in page.expect_response.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-16 02:02:34 +02:00
retoor fa8751a4ca Uppdate
DevPlace CI / test (push) Failing after 1h27m55s
2026-08-15 20:38:24 +02:00
retoorandClaude Sonnet 5 682be0861f Fail fast with one clear diagnostic when app_server dies mid-session
A shared-server crash mid-suite previously cascaded into hundreds of
opaque connection-refused errors across every later api/e2e test,
making the real cause invisible. pytest_runtest_setup now polls the
tracked subprocess and, on the first test after it exits, reports the
exit code plus the server's own log tail once instead of forcing every
subsequent test to fail blind.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 20:25:39 +02:00
retoorandClaude Sonnet 5 72e11db185 Ensure full projects column set in init_db to fix cross-process schema drift
Test-process reflections of the projects table cached a reduced schema
when they ran before the app server had ALTER-TABLE'd in website_url,
repo_url, cover_attachment_uid, logo_attachment_uid, platforms,
release_date, and demo_date, causing spurious KeyErrors under full-suite
ordering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-15 00:18:04 +02:00
retoor 37d23e8581 Update
DevPlace CI / test (push) Failing after 41m14s
2026-08-14 03:20:29 +02:00
retoor b97b5a7854 Waw 2026-08-13 12:59:53 +02:00
retoorandClaude Sonnet 5 45ad8e79ed Revert background color tokens to the original dark palette
PR #164 shifted --bg-primary/secondary/card/input/modal and the body
gradient to a purple-toned palette; restore the prior values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-13 12:59:53 +02:00
retoor 11c0cc66cc pdate 2026-08-13 12:59:53 +02:00
retoor 6514261730 Route container proxies through the leg that is actually reachable
The workspace editor hung for 60s and then 504'd. Three independent faults were
stacked behind that one symptom.

Reachability: editor_target delegated to proxy_target, which returns
CONTAINER_PROXY_HOST plus the published host port and never falls back to the
container. From inside the app container that address crosses docker0 into the
host INPUT chain, whose policy is DROP with an allow-list that does not include
the published port range, so the packet was dropped and the request hung rather
than being refused. Measured from the app container: container_ip:8443 answers
302, gateway:20006 is dropped. One shared reachable_target now prefers the
direct container leg and falls back to the published port, and editor_target
uses tunnel_target as services/containers/CLAUDE.md already required. The same
defect affected /p/{slug} ingress and every tunnel, since all three resolved
through proxy_target.

The recorded measurement that motivated the old order (container_ip times out,
gateway connects) no longer holds: make docker-attach puts the app on the
instances' bridge network, which is what makes the direct leg work.

Duplicate response headers: the forwarding core relayed the upstream Date and
Server alongside the ones the serving layer generates, so every proxied
response carried two of each. Both are singleton headers and duplicating them
is malformed HTTP.

Serialization: WorkspaceViewOut declared flag_reason and three sibling strings
as str, so a NULL column made the workspace page 500 for JSON clients.

Documents the two public hostnames and the devplace.net SSH tunnel, so a future
session does not conclude the site is down after pointing curl --resolve at an
address the hostname does not resolve to, and adds the layered procedure for
diagnosing a production failure.

Verified on production with Playwright over both hostnames: the code-server
login renders and the workbench loads. Suite: 3345 passed.
2026-08-13 12:59:53 +02:00
retoor ecb22f2b2d Merge pull request 'Dedicated project page' (#166) from blindxfish/devplacepy:project-page into master
DevPlace CI / test (push) Failing after 1h23m34s
Reviewed-on: #166
2026-08-13 12:49:33 +02:00
blindxfishandClaude Opus 5 265cb781f9 Match the cover and logo upload filter to the dp-upload contract
DevPlace CI / test (pull_request) Has been cancelled
The cover and logo widgets declared allowed-types as bare extensions
(png,jpg,jpeg,gif,webp), but dp-upload builds the candidate extension
with a leading dot before testing membership, so every selected file
was refused with "type is not allowed". The four widgets were the only
hardcoded lists in the codebase: every other call site passes
allowed_file_types(), which defaults to empty and therefore disables
the client filter entirely, which is why nothing else exposed the
mismatch.

Rather than dotting a duplicated literal in four places, the effective
list now comes from a new allowed_image_types() Jinja global that
intersects allowed_extensions() with IMAGE_EXTENSIONS. That reuses the
one server-side choke point, so the widget can never advertise a type
the upload gate would reject, and narrowing the admin allowed_file_types
setting narrows these widgets with it. IMAGE_EXTENSIONS rather than
POST_IMAGE_EXTENSIONS is the correct set here because the route guard
is _hero_attachment_uid, which accepts any is_image attachment, and
bmp/tiff both upload and pass it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 08:29:35 +02:00
blindxfishandClaude Fable 5 72e088c160 Dedicate the project page to the project
DevPlace CI / test (pull_request) Has been cancelled
The project detail page becomes a full project showcase built entirely
from existing platform mechanisms. One encompassing dark card wraps the
page; inner panels (tab bar, sidebar cards, devlog entries, comments)
sit one elevation lighter. The hero opens with a cover banner and an
optional logo tile, both plain attachment references
(cover_attachment_uid/logo_attachment_uid) uploaded through the
standard dp-upload attachment widget and linked via the existing
link_attachments choke point - the route validates each uid belongs to
the actor and is an image, and an empty value on edit keeps the current
one. The title block, type/platform chips and author row overlay the
banner behind a scrim with a dark text shadow, next to an owner-set
Visit Website CTA; website_url and repo_url are normalized in models
and render with rel noopener nofollow.

An anchor tab bar (Overview, Devlog, Screenshots when present,
Comments, Files) navigates the page. The main column keeps About, the
devlog timeline (with devlog_count and an owner Post update button
opening the shared composer preset to the devlog topic + project - the
form now lives once in _post_composer_form.html, included by feed.html
and project_detail.html), a Screenshots gallery built from image
attachments minus the cover/logo (thumbnails, lightbox, 12 rendered),
and the comment thread; the sidebar holds Links, Stats and the Author
card. Owners add gallery images from the More menu via
POST /projects/{slug}/screenshots (owner-only, audit
project.screenshots.add, Devii action project_add_screenshots, docs id
projects-screenshots). comment_count/devlog_count ride
ProjectDetailOut, the new fields ride ProjectOut, and the create/edit
faces (modals, Devii actions, API docs) carry them. The project
comment/files e2e tests scope their locators per the documented
dual-control idiom, and new unit/api/e2e tests cover URL normalization,
the counts, the hero attachment guard, the screenshots flow and the
preset composer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 23:00:12 +02:00
retoor 782bcec5bc ipdate
DevPlace CI / test (push) Failing after 1h9m6s
2026-08-10 03:12:43 +02:00
retoor f3b91ac75b Merge pull request 'Align the platform look and feel with the devplace main-branch design' (#164) from blindxfish/devplacepy:pr1-terminal-theme into master
DevPlace CI / test (push) Failing after 1h8m25s
Reviewed-on: #164
2026-08-10 00:28:42 +02:00
retoor 6cac64a3f6 Update
DevPlace CI / test (push) Has been cancelled
2026-08-10 00:23:20 +02:00
retoorandClaude Opus 5 2bdcf6528f Refuse to touch the production database without stated confirmation
data/devplace.db is the live database and make dev, make prod and the
Docker stack all share it, so an agent-initiated command that reaches it
is a production incident waiting for a typo. The hazard is invisible in
the command text: the script that prompted this named no path at all, it
imported devplacepy and therefore resolved config.DATA_DIR to the real
file. A path-pattern rule would have sailed straight past it.

The PreToolUse hook reads the script and judges it on content, so one
that points DEVPLACE_DATABASE_URL at a scratch file passes and an
unguarded one does not. It also refuses commands naming the database or a
production data directory, the management CLI, and python -m devplacepy.
The suite, the server targets and the mandated import gate stay free.
permissions.deny additionally refuses Write and Edit anywhere under data,
which the Bash hook cannot see.

The escape hatch is two-factor and cannot be self-served: without
confirmation the command is denied outright rather than prompted, and the
token that downgrades it to a prompt may only be added after the user has
confirmed in their own words. Verified against thirty-five commands, and
the heuristic is narrower than it looks because the repository path
itself contains the package name, so it matches an import statement
rather than the bare word.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 00:18:10 +02:00
retoorandClaude Opus 5 7e37122f9f Converge every account onto every policy agreement it has not declined
An instance kept production-identical for extended manual testing is
otherwise taxed forever by its own safety controls: five consents, a
versioned terms gate on every mutating request, and every account
predating the trust and safety commit reading terms_version NULL because
init_db deliberately never backfills it. AcceptanceService grants each
agreement to each account that has not declined it, so the instance stays
production byte for byte while nobody clicks the same dialog again. It is
opt-in, dry run by default, and one switch per agreement type.

The application is not allowed to know it exists. One registration line
in main.py is the only import anywhere, there is no route, schema,
template, Devii tool or environment flag, and a unit test greps the tree
and fails the suite if a second importer appears. The decline register
needs no storage: the ledger is append-only in effect, the service only
ever grants, so any withdrawn row was written by a human and that pair is
never touched again. No provenance column, nothing to observe.

Satisfaction is the gate's own expression, never a proxy, which is why
the ordering is created_at then id exactly as consent_state selects, and
why the live-account clauses are built with has_column: init_db ensures
terms_version and deletion_requested_at but not is_active, so a hardcoded
reference raises no such column on an instance where nobody was ever
suspended. Every write is one conditional statement decided on the real
rowcount, proven with sixteen processes racing one account to exactly one
ledger row and one audit row. The two existing audit keys carry it, with
actor kind service, because a service that silently mutated consent state
would be the worst possible exception to the append-only rule.

lensfl.md is the source brief accept.md records the design against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 00:17:47 +02:00
blindxfishandClaude Fable 5 3fca4be72e Flatten control accents: no gradient or glow on buttons, tabs, or the FAB
DevPlace CI / test (pull_request) Has been cancelled
The reference design uses flat primary buttons, so every interactive
control drops the brand gradient and the accent glow: .btn-primary
(flat accent, accent-hover on hover), the feed nav tabs, the projects
tabs, the sidebar active link, and the create-post FAB (which keeps a
neutral elevation shadow). Gradients remain only on decorative meters
and stripes (XP fills, progress bars, the project card top stripe).
The now-unused --glow-accent token is removed, and deepsearch.css
loses its var() fallback on the progress gradient.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 21:36:51 +02:00
blindxfishandClaude Fable 5 c09e6b328c Fix the member home layout: align the Code Farm promo, fill sparse post rows
The signed-in dashboard is a 1200px two-column grid, but the shared
Code Farm promo section stayed at the guest page's 960px centered
measure, so it floated misaligned under the dashboard columns. A
landing-game-dashboard modifier (applied only when a user is signed
in) stretches it to the dashboard's own measure and gutters. The
Latest Posts grid becomes repeat(auto-fit, minmax(320px, 1fr)) so a
single post fills the row instead of leaving a dead half-column while
two or more still lay out as the original two columns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 21:32:53 +02:00
blindxfishandClaude Fable 5 2921edd7f3 Retarget the theme to the devplace main-branch design (indigo + rust)
The reference design is the devplace Next.js main branch, not the
terminal branch the previous commit tracked. Swap the token values to
that design language: deep indigo page (#271b5b) over near-black indigo
cards (#13112a/#1a1736), #11101f hairlines, purple-tinted text ramp
(#e9e4ff/#c8c3e5/#9b93c9), burnt-orange primary accent (#b73f1e) with
a raspberry hover (#af3050) and a rust-to-plum brand gradient, white
on-accent text, soft rounded radii (8/12/16px), the reference's diffuse
soft/medium/strong shadows, a warm accent glow, and Trebuchet MS as the
UI font (system font - the vendored JetBrains Mono files are removed
along with their base.html link).

Follow-through updated to match: theme-color meta + PWA manifest
(#271b5b), offline page, avatar fallback SVG, statistics chart palette
(brand ramp), chat light-theme overrides, OG image + app icons
regenerated in the new palette, and the /docs/styles-colors guide.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 21:22:32 +02:00
blindxfishandClaude Fable 5 c19ff19de2 Align the platform look and feel with the devplace.net terminal design
Retheme every token in variables.css to the terminal design language:
near-black surfaces (#0a0a0f/#111118/#1a1a24), terminal green accent
(#22c55e) with dark on-accent text, gray text ramp, #222233 borders,
sharp 4-8px radii, neutral depth shadows, and JetBrains Mono as the
primary UI font (vendored latin woff2, no CDN).

Follow-through outside the token file: white-on-accent buttons/tabs now
use the new --on-accent token for contrast, three var() fallback
violations shipping the old palette are gone, the dead --bg-hover token
is replaced with --bg-card-hover, avatar fallback SVG, statistics chart
palette, chat light-theme overrides, offline page, PWA manifest,
theme-color meta, OG image + app icons regenerated in the new palette,
and the /docs/styles-colors style guide now documents the new values.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 20:55:46 +02:00
retoor 1a5fc9428a FUCKER
DevPlace CI / test (push) Failing after 1h3m34s
2026-08-09 11:39:53 +02:00
retoorandClaude Opus 5 c0742994cd Make the workspace editor reachable through the sub-path proxy
code-server runs authenticateOrigin on every websocket and resolves the
request host as Forwarded, then X-Forwarded-Host, then Host. The forward
core put the public host into additional_headers, but the websockets
client already writes its own Host for the real TCP target and Headers
appends, so the handshake carried two Host lines; Node keeps the first
(the internal gateway:port), the origin check failed, and code-server
answered 403. Because the browser socket was accepted before the upstream
was dialled, that surfaced as a 101 followed by 1011 and the editor died
on "the workbench failed to connect to the server". Dialling first and
carrying the public host in the connect URI fixes both planes.

The two header builders that had drifted apart are now one core, so a
websocket carries the same client and forwarded headers as an HTTP
request. Responses stream instead of buffering whole, which is what makes
a large tunnel download cost constant memory and lets SSE work; byte
accounting moved onto the completion callback. Subprotocols negotiate,
the upstream client is reused across requests, and the path and query are
forwarded byte-exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 11:26:17 +02:00
retoorandClaude Opus 5 91fac7fd67 Gate blocked actions behind an in-place terms acceptance dialog
A member whose account has not accepted the terms in force now gets one
dialog on the action they attempted instead of a dead-end refusal. The
client handler is the single TermsGate, wired into every Http POST helper
so the four optimistic controllers cannot swallow the gate into an error
flash, and the original request is replayed once the acceptance is
recorded. Reading the site and deleting an account stay unblocked.

apple.md is the source brief the compliance research documents reference.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 11:25:57 +02:00
retoor 8e9d3fad98 Add the trust and safety subsystem and the App Store compliance work
Implements the moderation and consent obligations a social platform carries,
so the web version and any client that speaks to it enforce the same rules.

Moderation core (services/moderation/, database/moderation.py): a reportable
target registry, the content filter and its choke points, the report queue with
atomic resolution, enforcement actions, consent tracking, maturity gating, and
account deletion with a grace window.

Surfaces: POST /reports plus the member report list, /admin/moderation and the
per-report admin view, /workspaces, terms acceptance at /auth/terms, consent and
account deletion under /profile, the report button and dialog partials, the
maturity gate, and the moderation stylesheet and ReportDialog client.

Every user-generated surface stays reportable by construction: new content tables
are registered in REPORTABLE_TARGETS or listed in UNREPORTABLE_TABLES with a
reason, and the registry test fails the suite on anything left unclassified.

Docs: community guidelines, content moderation, intellectual property, privacy,
terms, contact, and the admin-only moderation operations page, plus the
moderation API group and the Devii moderation actions.

Compliance record: applecomp.md is the requirement register, applechanges.md the
gap analysis against this codebase, and appleimpl.md the implementation design
they resolve to.

Tests cover the report flow, admin moderation, consent, account deletion, terms
acceptance, workspaces, and the registry invariant across the unit, api, and e2e
tiers.
2026-08-09 00:18:20 +02:00
retoor 68c2bbe387 Share one secret display style and add the architecture atlas
DevPlace CI / test (push) Failing after 1h0m46s
The API key block and the workspace editor password each carried their
own copy of the same monospace, break-all, boxed styling. Both now use a
single secret-value class in base.css, so a new secret surface inherits
the presentation instead of duplicating it.

ARCHITECTURE.md is a structural overview of the application derived from
the source tree, the live schema and the imported app object, linked
from the README.
2026-08-08 13:52:11 +02:00