Dedicated project page #166

Merged
retoor merged 2 commits from blindxfish/devplacepy:project-page into master 2026-08-13 12:49:34 +02:00
Contributor

Replaces #165, rebuilt from scratch on current master (782bcec) after the review there.

This is the project page only. The SEO layer from #165 is gone: no seo.py changes, no JSON-LD, no meta/schema work of any kind. git diff --stat origin/master...project-page touches no SEO file.

What it does

Turns /projects/{slug} into a dedicated project showcase instead of a generic detail row.

  • One encompassing dark shell (.project-shell on --bg-card) wraps hero, tab bar and the two-column body; inner panels sit one elevation lighter on --bg-secondary.
  • Hero: cover banner plus optional logo tile, with the title block, type/platform chips and author row overlaid behind a bottom scrim (text-shadow for readability), and an owner-set Visit Website CTA.
  • Anchor tab bar (.project-tabs) linking #about / #devlog / #screenshots / #comments / Files. Server-rendered anchors, no JS tab state.
  • Main column: About (description plus non-image attachments), Devlog (every post whose project_uid points here, rendered through the existing _post_card.html), Screenshots gallery, comment thread.
  • Sidebar: Links, Stats, Author.
  • Owner controls: a Post update button opening the composer preset to topic=devlog plus this project, and a More-menu Add screenshots flow.
  • New fields: website_url and repo_url, normalized by models.normalize_website_url (scheme-less input gets https://, non-http(s) rejected), rendered with rel="noopener nofollow".

Addressing the review on #165

  • Uploads ride the one existing pipeline. Cover, logo and screenshots are all attachment references (cover_attachment_uid, logo_attachment_uid), uploaded by the existing dp-upload widget to /uploads/upload and linked through attachments.link_attachments. No second upload path, no bespoke file handling. The route validates each uid via database.get_user_attachment (must exist, belong to the actor, be an image) before linking; a foreign or unknown uid is ignored rather than linked.
  • No markdown re-slicing. The only content.py addition is count_project_devlog, a count query. Rendering stays on render_content/render_title.
  • No comments or docstrings in source. Nothing under devplacepy/ adds either. Test docstrings follow the convention already in those same test files.
  • No camelCase in the database. The camelCase keys flagged on #165 were schema.org JSON-LD property names in the SEO layer, not columns. That layer is dropped entirely; the two new columns here are website_url and repo_url.

Composer duplication

The post composer form now lives once, in templates/_post_composer_form.html, included by both feed.html and project_detail.html with locals _composer_topic / _composer_project. The previous inline copy in feed.html is removed rather than forked.

Four faces

The one new route, POST /projects/{slug}/screenshots, is fanned out:

  • HTML: More-menu Add screenshots modal
  • Schema: ProjectScreenshotsForm, owner-only, audit event project.screenshots.add
  • Devii action: project_add_screenshots
  • docs_api: id projects-screenshots

comment_count / devlog_count ride ProjectDetailOut; the new project fields ride ProjectOut.

Testing

Full suite on this branch: 3334 passed, 9 failed. The 9 failures are tests/api/projects/workspace.py needing the docker CLI, and they reproduce identically on untouched master in the same environment. Environmental, not regressions.

New coverage: tests/api/projects/devlog.py (detail counts, cover/logo resolution, foreign-uid rejection, screenshots route incl. non-owner refusal), tests/api/projects/edit.py, tests/e2e/projects/devlog.py (Stats card, owner Post update control, guest/non-owner absence), tests/unit/models.py (URL normalization).

Replaces #165, rebuilt from scratch on current master (782bcec) after the review there. This is the project page only. The SEO layer from #165 is gone: no `seo.py` changes, no JSON-LD, no meta/schema work of any kind. `git diff --stat origin/master...project-page` touches no SEO file. ## What it does Turns `/projects/{slug}` into a dedicated project showcase instead of a generic detail row. - **One encompassing dark shell** (`.project-shell` on `--bg-card`) wraps hero, tab bar and the two-column body; inner panels sit one elevation lighter on `--bg-secondary`. - **Hero**: cover banner plus optional logo tile, with the title block, type/platform chips and author row overlaid behind a bottom scrim (text-shadow for readability), and an owner-set **Visit Website** CTA. - **Anchor tab bar** (`.project-tabs`) linking `#about` / `#devlog` / `#screenshots` / `#comments` / Files. Server-rendered anchors, no JS tab state. - **Main column**: About (description plus non-image attachments), Devlog (every post whose `project_uid` points here, rendered through the existing `_post_card.html`), Screenshots gallery, comment thread. - **Sidebar**: Links, Stats, Author. - **Owner controls**: a **Post update** button opening the composer preset to `topic=devlog` plus this project, and a More-menu **Add screenshots** flow. - **New fields**: `website_url` and `repo_url`, normalized by `models.normalize_website_url` (scheme-less input gets `https://`, non-http(s) rejected), rendered with `rel="noopener nofollow"`. ## Addressing the review on #165 - **Uploads ride the one existing pipeline.** Cover, logo and screenshots are all attachment references (`cover_attachment_uid`, `logo_attachment_uid`), uploaded by the existing `dp-upload` widget to `/uploads/upload` and linked through `attachments.link_attachments`. No second upload path, no bespoke file handling. The route validates each uid via `database.get_user_attachment` (must exist, belong to the actor, be an image) before linking; a foreign or unknown uid is ignored rather than linked. - **No markdown re-slicing.** The only `content.py` addition is `count_project_devlog`, a count query. Rendering stays on `render_content`/`render_title`. - **No comments or docstrings in source.** Nothing under `devplacepy/` adds either. Test docstrings follow the convention already in those same test files. - **No camelCase in the database.** The camelCase keys flagged on #165 were schema.org JSON-LD property names in the SEO layer, not columns. That layer is dropped entirely; the two new columns here are `website_url` and `repo_url`. ## Composer duplication The post composer form now lives once, in `templates/_post_composer_form.html`, included by both `feed.html` and `project_detail.html` with locals `_composer_topic` / `_composer_project`. The previous inline copy in `feed.html` is removed rather than forked. ## Four faces The one new route, `POST /projects/{slug}/screenshots`, is fanned out: - HTML: More-menu **Add screenshots** modal - Schema: `ProjectScreenshotsForm`, owner-only, audit event `project.screenshots.add` - Devii action: `project_add_screenshots` - docs_api: id `projects-screenshots` `comment_count` / `devlog_count` ride `ProjectDetailOut`; the new project fields ride `ProjectOut`. ## Testing Full suite on this branch: **3334 passed, 9 failed**. The 9 failures are `tests/api/projects/workspace.py` needing the docker CLI, and they reproduce identically on untouched master in the same environment. Environmental, not regressions. New coverage: `tests/api/projects/devlog.py` (detail counts, cover/logo resolution, foreign-uid rejection, screenshots route incl. non-owner refusal), `tests/api/projects/edit.py`, `tests/e2e/projects/devlog.py` (Stats card, owner Post update control, guest/non-owner absence), `tests/unit/models.py` (URL normalization).
blindxfish added 1 commit 2026-08-13 08:07:00 +02:00
Dedicate the project page to the project
Some checks are pending
DevPlace CI / test (pull_request) Blocked by required conditions
72e088c160
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>
blindxfish added 1 commit 2026-08-13 08:49:59 +02:00
Match the cover and logo upload filter to the dp-upload contract
Some checks are pending
DevPlace CI / test (pull_request) Blocked by required conditions
265cb781f9
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>
retoor merged commit ecb22f2b2d into master 2026-08-13 12:49:34 +02:00
Sign in to join this conversation.
No reviewers
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: retoor/devplacepy#166
No description provided.