Files
devplacepy/devplacepy/templates/CLAUDE.md
T
blindxfishandClaude Fable 5 72e088c160 Dedicate the project page to the project
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

8.2 KiB

This file documents CDN libraries, the emoji picker, the modal system, and shared template partials under templates/. Claude Code auto-loads it whenever a file under this directory is read or edited.

Templates and frontend (general rules)

  • All routers MUST import the shared templates from devplacepy.templating. Do NOT instantiate Jinja2Templates per router - globals (avatar_url, get_unread_count, get_user_projects, format_date) are registered on that single instance.
  • Templates extend base.html, add page CSS via {% block extra_head %}, page JS via {% block extra_js %}.
  • Nav active state is the nav_active Jinja global, never an inline path check. nav_active(request, *prefixes, exact=False) (templating.py) returns "active" (or "") when request.url.path equals a prefix or is a sub-path of it (exact=True matches only the exact path, e.g. the admin Dashboard root). Every topnav/sidebar link uses it (class="topnav-link {{ nav_active(request, '/projects') }}"); do NOT hand-write {% if request.url.path == ... or request.url.path.startswith(...) %}active{% endif %}.

CDN Libraries

Loaded via <script> tags in base.html. ALL must use defer to avoid blocking DOMContentLoaded:

<script defer src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script defer src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/highlight.min.js"></script>
<script defer src="/static/vendor/purify.min.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js"></script>
<script defer src="/static/js/ContentRenderer.js"></script>
<script defer src="/static/js/EmojiPicker.js"></script>
<script type="module" src="/static/js/Application.js"></script>

Never use <script> without defer for CDN libraries - they block HTML parsing and cause wait_until="domcontentloaded" to timeout in Playwright tests.

Emoji Picker

Uses emoji-picker-element web component (Discord-style, searchable, skin tones):

  • EmojiPickerElement.js is the single wrapper around the vendored element: load() lazily imports the module on first open, create(onSelect) builds an <emoji-picker> wired to the vendored data.json and calls back with the chosen unicode. Never instantiate <emoji-picker> or hardcode its data source anywhere else.
  • EmojiPicker.js is the textarea consumer: it wraps the element in a toggle panel and inserts unicode at cursor position
  • Added to all .comment-form textarea and .emoji-picker-target elements
  • ReactionBar.js is the second consumer: the + button in _reaction_bar.html mounts the same element into .reaction-picker so any emoji can be used as a reaction
  • The old &#x1F600; emoji button has been removed from all templates

Modal System

Application.js initModals() toggles a .visible CSS class on .modal-overlay; the CSS rule .modal-overlay.visible { display: flex; } handles visibility:

// CORRECT - toggle the .visible class on the modal:
modal.classList.add("visible");    // show
modal.classList.remove("visible"); // hide

Triggers usually have href="#", so call e.preventDefault() in click handlers. Always call e.preventDefault() on [data-modal] click handlers since trigger elements often have href="#":

trigger.addEventListener("click", (e) => {
    e.preventDefault();
    modal.style.display = "flex";
});

.modal-close is wired generically by Application.js - no inline JS needed in templates for basic modals.

Do NOT hand-write the overlay/header markup. Use the shared macro in templates/_macros.html:

{% from "_macros.html" import modal %}
{% call modal('create-post-modal', 'Create New Post') %}{# wide=true for modal-card-wide #}
    <form ...> ... <div class="modal-footer">...</div> </form>
{% endcall %}

Shared template partials

Reuse these via {% set _x = ... %}{% include %} (the _avatar_link.html convention) instead of copy-pasting markup:

  • _post_composer_form.html - the create-post form (topic selector, content/title, project select, attachments, poll builder, footer). Locals: _composer_topic (preselected topic, default random), _composer_project (preselected project uid or ""). Wrapped in the modal() macro by feed.html (Create New Post) and project_detail.html (owner-only Post an update, preset to devlog + the project). Never fork a second copy of this form.
  • _post_votes.html - post +/- vote bar. Locals: _uid, _my_vote, _count.
  • _star_vote.html - project/gist star button. Locals: _type (project|gist), _uid, _my_vote, _count, _btn_class, optional _stop (adds data-stop-propagation). The star glyph ( when .voted) comes from the vote-star CSS class via ::before (base.css) - do not put a literal star in markup.
  • _post_header.html - post author/avatar/time header (.post-header). Locals: _author, _time.
  • _topic_selector.html - topic radio group. Locals: _topics, _selected.
  • _quiz_actions.html - the hub's New quiz / Create quiz with Devii card. Needs user; the Devii button carries data-devii-prompt (the platform-wide prompt-seeding attribute read by DeviiTerminal.bindTriggers).
  • _quiz_scoreboard.html - the cross-quiz scoreboard rail plus the Your progress card. Needs scoreboard, viewer_standing, viewer_progress, user.
  • _quiz_card.html - one quiz listing card. Needs item (a QuizListItemOut-shaped dict) and user.
  • _quiz_question.html - one question in builder-preview or player mode. Locals: _question, _mode (builder|player), plus _attempt_url and answer_max_chars in player mode.
  • _quiz_answer_review.html - one reviewed answer on the results screen. Local: _question.
  • _quiz_settings_fields.html - the shared quiz settings fieldset. Optional local _quiz seeds the current values.
  • _report_button.html - the Report control (and Block, when _owner_name is given) for any content action bar. Locals: _type, _uid, _owner (owner uid - the control hides on your own content), _owner_name (optional), _class (the surrounding surface's button class). Included at fifteen sites; a new content surface MUST include it (see the root CLAUDE.md rule).
  • _report_dialog.html - the one report dialog, included once in base.html for signed-in users and driven by ReportDialog.js. Reasons come from the REPORT_REASONS Jinja global; never hand-roll a second dialog.
  • _maturity_gate.html - the interstitial rendered in place of a maturity-labelled item. Local: _level. Guard the include with the maturity_hidden(level, user) global; the item's maturity is attached by enrich_items/load_detail.

Vote button styles live ONCE in feed.css (.post-action-btn) - never redefine them in post.css. Page-specific CSS goes in a static/css/*.css file referenced from {% block extra_head %}, never an inline <style> block. All CSS conventions (tokens, --z-* stacking bands, breakpoints, reduced motion) are in devplacepy/static/css/CLAUDE.md.

For clickable avatars/usernames, reuse templates/_avatar_link.html and templates/_user_link.html via {% include %} with _user, _size, _size_class locals.

The three public listings (/feed, /gists, /projects) share one search box: the templates/_sidebar_search.html partial (included at the top of .sidebar-card with _action/_placeholder/_hidden locals) plus the database.text_search_clause(table, search, fields, author_field) data helper. Each listing matches its text fields PLUS the author username: pass author_field="user_uid" and the helper resolves matching usernames to uids via database.get_uids_by_username_match(search) and OR-includes user_uid IN (...) (no JOIN, no separate ilike). Any new listing with a left filter panel reuses both - never re-inline an ilike search clause, never add a JOIN for author matching, and never hand-roll another search form. See devplacepy/routers/CLAUDE.md -> Listing search.

Modal pattern

Application.js initModals() toggles a .visible CSS class on .modal-overlay; the CSS rule .modal-overlay.visible { display: flex; } handles visibility. Triggers usually have href="#", so call e.preventDefault() in click handlers. .modal-close is wired generically - no inline JS needed.