Skip to main content

max / makenotwork

Delete the dead Blog dashboard tab Max ruled it goes only once the live copy is a described quasi element rather than before. 630682cc served project_content from the description layer on both boxes today, and quasi::project_content renders the blog table itself, so the condition is met. Gone: the /dashboard/project/{slug}/tabs/blog route, the project_tab_blog handler, ProjectBlogTabTemplate and its registration, and the 44-line partial. Also the import the struct left behind, and blog_delete_act's module doc, which described the deleted template as a live second caller in the present tense. The route was reached by nothing. The only mention of /tabs/blog anywhere in templates, static, frontend or src is a test asserting it is NOT in the described tab strip, because blog is reached from the content panel. The inline table in project_content.html stays, as the ruling asks, and its delete button still calls quasi::blog_delete_act -- one caller now, not two. Neither seal moves: the partial carried no hx-confirm and no data-action. Checking project_promotions in the same pass, as the ruling also asks: its partial is live, included by project_monetization.html, but its route measures as unreferenced exactly as this one did. Filed as f698064d rather than removed here, because the shape is inverted -- there the route may go and the partial must stay -- and /tabs/members and /tabs/subscriptions want the same look.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-26 16:54 UTC
Signed with PGP, not checked
Commit: 5394a7cdd389c57300f7176395a1fb75150541f9
Parent: 32eac1d
7 files changed, +17 insertions, -115 deletions
@@ -2,12 +2,19 @@
2 2 //!
3 3 //! Shape 8's first conversion that did not have to wait for a screen
4 4 //! (`b279b9eb`), and the second thing the glue-module ruling bought
5 - //! (`27d5e5b8`, reasoning in [`super::widgets`]). The same button is written
6 - //! twice — `templates/partials/tabs/project_content.html` and
7 - //! `templates/partials/tabs/project_blog.html` — over the same route, with the
5 + //! (`27d5e5b8`, reasoning in [`super::widgets`]). The same button was written
6 + //! twice, in `templates/partials/tabs/project_content.html` and
7 + //! `templates/partials/tabs/project_blog.html`, over the same route with the
8 8 //! same prompt, in two tabs of the same dashboard. Described once, called from
9 9 //! both.
10 10 //!
11 + //! **One caller now.** The blog tab was dead — a route no markup reached, kept
12 + //! only because it held the second copy of this table — and it was deleted at
13 + //! the `project_content` flip (`6077d0d9`, 2026-08-26). So the duplication this
14 + //! module was written to end is gone by subtraction as well as by description.
15 + //! That is not a reason to inline it back: `project_content.html`'s own copy
16 + //! becomes a described element in its turn, and this is where it goes.
17 + //!
11 18 //! # What the description says that the markup did not
12 19 //!
13 20 //! That the act is destructive, and what to ask before doing it.
@@ -236,7 +236,6 @@
236 236 PayoutSummaryPartialTemplate,
237 237 ProjectSettingsTabTemplate,
238 238 ProjectCodeTabTemplate,
239 - ProjectBlogTabTemplate,
240 239 ProjectSubscriptionsTabTemplate,
241 240 ProjectMembersTabTemplate,
242 241 ProjectMonetizationTabTemplate,
@@ -516,15 +516,6 @@
516 516 pub can_push: bool,
517 517 }
518 518
519 - /// Dashboard blog tab partial.
520 - #[derive(Template)]
521 - #[template(path = "partials/tabs/project_blog.html")]
522 - pub struct ProjectBlogTabTemplate {
523 - pub project_id: String,
524 - pub project_slug: String,
525 - pub posts: Vec<BlogPostDashboardRow>,
526 - }
527 -
528 519 /// Dashboard subscriptions tab partial for tier management.
529 520 #[derive(Template)]
530 521 #[template(path = "partials/tabs/project_subscriptions.html")]
@@ -44,7 +44,9 @@
44 44 "content",
45 45 "analytics",
46 46 "settings",
47 - "blog",
47 + // "blog" was here until 2026-08-26. The dashboard blog tab was dead
48 + // markup over a route nothing reached, and it went at the
49 + // project_content flip (`6077d0d9`).
48 50 "subscriptions",
49 51 ];
50 52 for tab in tabs {
@@ -138,10 +138,6 @@
138 138 "/dashboard/project/{slug}/tabs/settings",
139 139 get(project_tabs::project_tab_settings),
140 140 )
141 - .route_get(
142 - "/dashboard/project/{slug}/tabs/blog",
143 - get(project_tabs::project_tab_blog),
144 - )
145 141 .route_get(
146 142 "/dashboard/project/{slug}/tabs/monetization",
147 143 get(project_tabs::project_tab_monetization),
@@ -14,11 +14,10 @@
14 14 error::{AppError, Result},
15 15 helpers,
16 16 templates::{
17 - LinkedRepoView, ProjectAnalyticsTabTemplate, ProjectBlogTabTemplate,
18 - ProjectCodeTabTemplate, ProjectContentTabTemplate, ProjectMembersTabTemplate,
19 - ProjectMonetizationTabTemplate, ProjectOverviewTabTemplate, ProjectPromotionsTabTemplate,
20 - ProjectSettingsTabTemplate, ProjectSubscriptionsTabTemplate, ProjectSyncKitTabTemplate,
21 - RepoCollaboratorView,
17 + LinkedRepoView, ProjectAnalyticsTabTemplate, ProjectCodeTabTemplate,
18 + ProjectContentTabTemplate, ProjectMembersTabTemplate, ProjectMonetizationTabTemplate,
19 + ProjectOverviewTabTemplate, ProjectPromotionsTabTemplate, ProjectSettingsTabTemplate,
20 + ProjectSubscriptionsTabTemplate, ProjectSyncKitTabTemplate, RepoCollaboratorView,
22 21 },
23 22 types::{
24 23 BlogPostDashboardRow, ContentItem, Project, ProjectMemberRow, PromoCodeRow, StatCard,
@@ -500,54 +499,6 @@
500 499 ))
501 500 }
502 501
503 - /// Render the HTMX partial for the project blog tab.
504 - #[tracing::instrument(skip_all, name = "project_tabs::project_tab_blog")]
505 - pub(super) async fn project_tab_blog(
506 - State(db): State<PgPool>,
507 - AuthUser(session_user): AuthUser,
508 - headers: HeaderMap,
509 - Path(slug): Path<String>,
510 - ) -> Result<axum::response::Response> {
511 - let (db_project, generation) =
512 - match resolve_project_etag(&db, session_user.id, &slug, &headers).await? {
513 - Ok(pair) => pair,
514 - Err(not_modified) => return Ok(not_modified),
515 - };
516 -
517 - let db_posts = db::blog_posts::get_blog_posts_by_project(&db, db_project.id).await?;
518 -
519 - let posts: Vec<BlogPostDashboardRow> = db_posts
520 - .into_iter()
521 - .map(|p| BlogPostDashboardRow {
522 - id: p.id.to_string(),
523 - title: p.title,
524 - slug: p.slug.to_string(),
525 - status: if p.published_at.is_some() {
526 - "Published".to_string()
527 - } else {
528 - "Draft".to_string()
529 - },
530 - status_tone: if p.published_at.is_some() {
531 - crate::types::BadgeStatus::Live.tone()
532 - } else {
533 - crate::types::BadgeStatus::Pending.tone()
534 - },
535 - published_at: p
536 - .published_at
537 - .map_or_else(|| "-".to_string(), |d| d.format("%b %d, %Y").to_string()),
538 - })
539 - .collect();
540 -
541 - Ok(helpers::with_etag(
542 - generation,
543 - ProjectBlogTabTemplate {
544 - project_id: db_project.id.to_string(),
545 - project_slug: db_project.slug.to_string(),
546 - posts,
547 - },
548 - ))
549 - }
550 -
551 502 /// Render the HTMX partial for the project promotions tab (promo codes).
552 503 #[tracing::instrument(skip_all, name = "project_tabs::project_tab_promotions")]
553 504 pub(super) async fn project_tab_promotions(
@@ -1,44 +1,0 @@
1 - <div class="tab-docs"><a href="/docs/blog">Docs: Blog &rarr;</a></div>
2 -
3 - <div class="content-header">
4 - <h2 class="subsection-title">Blog Posts</h2>
5 - <a href="/dashboard/project/{{ project_slug }}/blog/new">
6 - <button class="btn-primary">New Post</button>
7 - </a>
8 - </div>
9 -
10 - {% if posts.is_empty() %}
11 - <div class="empty-state blog-empty-state">
12 - <p>No blog posts yet. Start writing to engage your audience.</p>
13 - <p class="blog-empty-hint">Share behind-the-scenes updates, release notes, or stories. Posts appear on your project page and in your RSS feed, bringing fans back to your storefront.</p>
14 - </div>
15 - {% else %}
16 - <table class="data-table well">
17 - <thead>
18 - <tr>
19 - <th class="col-45">Title</th>
20 - <th class="col-15">Status</th>
21 - <th class="col-20">Published</th>
22 - <th class="col-20">Actions</th>
23 - </tr>
24 - </thead>
25 - <tbody>
26 - {% for post in posts %}
27 - <tr id="post-row-{{ post.id }}">
28 - <td>
29 - <a href="/p/{{ project_slug }}/blog/{{ post.slug }}" class="blog-post-title-link">{{ post.title }}</a>
30 - </td>
31 - <td><span class="badge" data-tone="{{ post.status_tone }}">{{ post.status }}</span></td>
32 - <td>{{ post.published_at }}</td>
33 - <td>
34 - <div class="blog-post-actions">
35 - <a href="/p/{{ project_slug }}/blog/{{ post.slug }}" class="btn-secondary small">View</a>
36 - <a href="/dashboard/project/{{ project_slug }}/blog/new?post={{ post.id }}" class="btn-secondary small">Edit</a>
37 - {{ crate::quasi::blog_delete_act::html(post.id.to_string().as_str())|safe }}
38 - </div>
39 - </td>
40 - </tr>
41 - {% endfor %}
42 - </tbody>
43 - </table>
44 - {% endif %}