max / makenotwork
17 files changed,
+209 insertions,
-14 deletions
| @@ -3445,7 +3445,7 @@ dependencies = [ | |||
| 3445 | 3445 | ||
| 3446 | 3446 | [[package]] | |
| 3447 | 3447 | name = "makenotwork" | |
| 3448 | - | version = "0.5.11" | |
| 3448 | + | version = "0.5.13" | |
| 3449 | 3449 | dependencies = [ | |
| 3450 | 3450 | "anyhow", | |
| 3451 | 3451 | "argon2", |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | [package] | |
| 2 | 2 | name = "makenotwork" | |
| 3 | - | version = "0.5.12" | |
| 3 | + | version = "0.5.13" | |
| 4 | 4 | edition = "2024" | |
| 5 | 5 | license-file = "LICENSE" | |
| 6 | 6 |
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | # Makenotwork TODO | |
| 2 | 2 | ||
| 3 | 3 | ## Status | |
| 4 | - | v0.5.11 deployed 2026-05-10. Audit grade A (Run 24). ~88K LOC, 1,933 tests, 0 warnings. Migration 107. Sprints 1-9 complete (see `todo_done.md`). | |
| 4 | + | v0.5.12 deployed 2026-05-10. Audit grade A (Run 24). ~88K LOC, 1,933 tests, 0 warnings. Migration 110. Sprints 1-9 complete (see `todo_done.md`). Content seeded: AF 0.4.0 + GO 0.3.1 on discover page. | |
| 5 | 5 | ||
| 6 | 6 | Human tasks in `human_todo.md`. Completed items in `todo_done.md`. | |
| 7 | 7 | ||
| @@ -21,6 +21,14 @@ Priority order. See `human_todo.md` for the full manual testing feature map. | |||
| 21 | 21 | ||
| 22 | 22 | --- | |
| 23 | 23 | ||
| 24 | + | ## Upload Improvements (Post-Launch) | |
| 25 | + | ||
| 26 | + | - [ ] **Background uploads**: allow navigating away from the Files tab during upload. Track upload state server-side (pending_uploads table exists). Show upload status in a persistent UI element (toast or header badge) so video/large-file creators aren't stuck on the page. | |
| 27 | + | - [ ] **Multipart upload**: split large files into chunks and upload in parallel for higher throughput. Current single-PUT caps at ~300 Mbps. Needed for video creators on fast connections. | |
| 28 | + | - [ ] **Desktop/CLI bulk upload**: power-user tool for uploading multiple files, versions, or large assets. Candidates: mnw-cli TUI, or a dedicated uploader binary. Would use multipart upload natively. | |
| 29 | + | ||
| 30 | + | --- | |
| 31 | + | ||
| 24 | 32 | ## Deferred from Sprints | |
| 25 | 33 | ||
| 26 | 34 | - [ ] Add bulk rename operation (Sprint 2) | |
| @@ -83,6 +91,7 @@ Remaining open items from Runs 21-24 and Code Fuzz (2026-05-08). All SERIOUS ite | |||
| 83 | 91 | - [ ] Git browser: add discover/follow integration | |
| 84 | 92 | ||
| 85 | 93 | ### Global UX | |
| 94 | + | - [ ] Find a better place for the keyboard shortcuts help button (removed from header nav) | |
| 86 | 95 | - [ ] Add toast stacking for multiple notifications | |
| 87 | 96 | - [ ] Add "New" badge/dot indicator on recently launched features | |
| 88 | 97 | - [ ] Auto-show "What's New" modal on major version bumps |
| @@ -4,6 +4,48 @@ Items moved from todo.md. See git history for implementation details. | |||
| 4 | 4 | ||
| 5 | 5 | --- | |
| 6 | 6 | ||
| 7 | + | ## v0.5.12 Content Seeding Session (2026-05-10) | |
| 8 | + | ||
| 9 | + | ### Build Infrastructure | |
| 10 | + | - [x] Set up pop-os as MNW build gate (SSH key, clone, migrations, build-gate.sh) | |
| 11 | + | - [x] Set up windows-x86 for app builds (Rust, VS2022, Node, repos cloned) | |
| 12 | + | - [x] Fix astra SSH host key, verify Rust 1.95 on all machines | |
| 13 | + | - [x] Update local Rust 1.93 → 1.95, fix 88 clippy warnings (collapsible_if, map_or, sort_by_key) | |
| 14 | + | - [x] Build all 18 artifacts: 3 apps x (macOS + Linux aarch64 + Linux x86_64 + Windows) | |
| 15 | + | - [x] Create deploy.md docs for all projects + root overview at _meta/docs/deploy.md | |
| 16 | + | - [x] Cross-platform build-css.js replaces shell-only beforeBuildCommand for GO | |
| 17 | + | ||
| 18 | + | ### Server Fixes (migrations 108-110) | |
| 19 | + | - [x] FIX: CSP `script-src 'self'` blocked all inline scripts — add `'unsafe-inline'` | |
| 20 | + | - [x] FIX: Caddy duplicate CSP headers (more restrictive intersection) — remove Caddy CSP | |
| 21 | + | - [x] FIX: `connect-src 'self'` blocked S3 uploads — add S3/CDN/Stripe domains | |
| 22 | + | - [x] FIX: upload.js not loaded in wizard templates — add to project + item wizards | |
| 23 | + | - [x] FIX: Cloudflare cached 404s for static JS — add cache-busting query params | |
| 24 | + | - [x] FIX: Version upload progress/error invisible for existing-version flow — move outside form | |
| 25 | + | - [x] FIX: Item update API returned raw JSON for HTMX requests — return "Saved." HTML | |
| 26 | + | - [x] FIX: Generic file upload in item wizard had no JS handler — add full upload logic | |
| 27 | + | ||
| 28 | + | ### New Features | |
| 29 | + | - [x] Multi-file version upload with per-file labels (e.g. "macOS (arm)", "Linux (x86_64)") | |
| 30 | + | - [x] Upload queue display with per-file status during batch upload | |
| 31 | + | - [x] Auto-guess labels from filenames (detects platform + arch) | |
| 32 | + | - [x] Multiple current versions per item (drop unique constraint, migration 109) | |
| 33 | + | - [x] Version labels (migration 108) | |
| 34 | + | - [x] Version delete endpoint (DELETE /api/items/{id}/versions/{version_id}) | |
| 35 | + | - [x] Show filenames in version table | |
| 36 | + | - [x] Bundle inline item creation (POST /api/items/{id}/bundle/create-child) | |
| 37 | + | - [x] Hide Files tab for bundle items | |
| 38 | + | - [x] AI disclosure at project level (migration 110), inherited by items | |
| 39 | + | - [x] Image upload on project settings tab and item details tab | |
| 40 | + | - [x] Redesigned public item page: "Downloads" section with version + label + size | |
| 41 | + | ||
| 42 | + | ### Content Seeding | |
| 43 | + | - [x] AF 0.4.0 published — 4 platform builds uploaded | |
| 44 | + | - [x] GO 0.3.1 published — 4 platform builds uploaded | |
| 45 | + | - [x] Discover page verified — 2 projects, categories, filters working | |
| 46 | + | ||
| 47 | + | --- | |
| 48 | + | ||
| 7 | 49 | ## v0.5.11 Deploy Fixes (2026-05-10) | |
| 8 | 50 | ||
| 9 | 51 | - [x] FIX: `integrity.rs` referenced non-existent `updated_at` on `creator_subscriptions` — changed to `current_period_end` |
| @@ -11,8 +11,8 @@ Go to **Account Settings** and click **Pause Creator Account**. You'll see a con | |||
| 11 | 11 | ## What Changes When You Pause | |
| 12 | 12 | ||
| 13 | 13 | **For you:** | |
| 14 | - | - Your creator tier subscription is canceled (no more monthly fee) | |
| 15 | - | - New purchases, subscriptions, and tips are blocked | |
| 14 | + | - Your creator tier membership is canceled (no more monthly fee) | |
| 15 | + | - New purchases, memberships, and tips are blocked | |
| 16 | 16 | - Your content remains hosted indefinitely | |
| 17 | 17 | - Your profile stays visible with a notice that you're on break | |
| 18 | 18 | ||
| @@ -24,10 +24,10 @@ Go to **Account Settings** and click **Pause Creator Account**. You'll see a con | |||
| 24 | 24 | ||
| 25 | 25 | ## Resuming | |
| 26 | 26 | ||
| 27 | - | Re-subscribe to a creator tier. When your new subscription activates, your account is automatically unpaused: | |
| 27 | + | Re-subscribe to a creator tier. When your new membership activates, your account is automatically unpaused: | |
| 28 | 28 | ||
| 29 | 29 | - The pause flag is cleared | |
| 30 | - | - Active fan subscriptions that haven't yet expired are un-canceled (they continue as normal) | |
| 30 | + | - Active fan memberships that haven't yet expired are un-canceled (they continue as normal) | |
| 31 | 31 | - New purchases are accepted again | |
| 32 | 32 | ||
| 33 | 33 | There is no separate "resume" button. Subscribing to a tier is the resume action. | |
| @@ -38,7 +38,7 @@ There is no separate "resume" button. Subscribing to a tier is the resume action | |||
| 38 | 38 | |---|---|---| | |
| 39 | 39 | | Content hosted | Indefinitely | Removed after 90 days | | |
| 40 | 40 | | Fan purchases accessible | Yes | Yes (downloads already made) | | |
| 41 | - | | Fan subscriptions | Expire at period end | Canceled immediately | | |
| 41 | + | | Fan memberships | Expire at period end | Canceled immediately | | |
| 42 | 42 | | Resume | Re-subscribe to tier | Not reversible | | |
| 43 | 43 | | Monthly fee | None while paused | N/A | | |
| 44 | 44 |
| @@ -864,6 +864,8 @@ mod tests { | |||
| 864 | 864 | price_cents, | |
| 865 | 865 | pwyw_min_cents, | |
| 866 | 866 | license_verification_enabled: false, | |
| 867 | + | ai_tier: db::AiTier::Handmade, | |
| 868 | + | ai_disclosure: None, | |
| 867 | 869 | } | |
| 868 | 870 | } | |
| 869 | 871 |
| @@ -23,7 +23,7 @@ pub struct FeedQuery { | |||
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | 25 | /// Build a sliding window of page numbers for pagination controls. | |
| 26 | - | fn build_pagination_range(current_page: u32, total_pages: u32) -> Vec<u32> { | |
| 26 | + | pub(super) fn build_pagination_range(current_page: u32, total_pages: u32) -> Vec<u32> { | |
| 27 | 27 | if total_pages <= constants::PAGINATION_WINDOW_SIZE { | |
| 28 | 28 | (1..=total_pages).collect() | |
| 29 | 29 | } else { |
| @@ -10,6 +10,7 @@ use tower_sessions::Session; | |||
| 10 | 10 | ||
| 11 | 11 | use crate::{ | |
| 12 | 12 | auth::{AuthUser, MaybeUser}, | |
| 13 | + | constants, | |
| 13 | 14 | db, | |
| 14 | 15 | error::{AppError, Result}, | |
| 15 | 16 | helpers::{self, get_csrf_token}, | |
| @@ -152,6 +153,46 @@ pub(super) async fn library_tab_purchases( | |||
| 152 | 153 | Ok(LibraryPurchasesTabTemplate { purchases }) | |
| 153 | 154 | } | |
| 154 | 155 | ||
| 156 | + | /// HTMX partial: library feed tab. | |
| 157 | + | #[tracing::instrument(skip_all, name = "landing::library_tab_feed")] | |
| 158 | + | pub(super) async fn library_tab_feed( | |
| 159 | + | State(state): State<AppState>, | |
| 160 | + | AuthUser(user): AuthUser, | |
| 161 | + | Query(query): Query<super::feed::FeedQuery>, | |
| 162 | + | ) -> Result<impl IntoResponse> { | |
| 163 | + | use crate::templates::LibraryFeedTabTemplate; | |
| 164 | + | ||
| 165 | + | let page = query.page.unwrap_or(1).max(1); | |
| 166 | + | let offset = ((page - 1) * constants::FEED_PAGE_SIZE) as i64; | |
| 167 | + | ||
| 168 | + | let total_items = db::follows::count_followed_feed_items(&state.db, user.id).await? as u32; | |
| 169 | + | let total_pages = (total_items + constants::FEED_PAGE_SIZE - 1) / constants::FEED_PAGE_SIZE.max(1); | |
| 170 | + | ||
| 171 | + | let db_items = db::follows::get_followed_feed_items( | |
| 172 | + | &state.db, | |
| 173 | + | user.id, | |
| 174 | + | constants::FEED_PAGE_SIZE as i64, | |
| 175 | + | offset, | |
| 176 | + | ) | |
| 177 | + | .await?; | |
| 178 | + | ||
| 179 | + | let items: Vec<DiscoverItem> = db_items.into_iter().map(DiscoverItem::from).collect(); | |
| 180 | + | ||
| 181 | + | let showing_start = if total_items == 0 { 0 } else { offset as u32 + 1 }; | |
| 182 | + | let showing_end = (offset as u32 + constants::FEED_PAGE_SIZE).min(total_items); | |
| 183 | + | let pagination_range = super::feed::build_pagination_range(page, total_pages); | |
| 184 | + | ||
| 185 | + | Ok(LibraryFeedTabTemplate { | |
| 186 | + | items, | |
| 187 | + | total_items, | |
| 188 | + | current_page: page, | |
| 189 | + | total_pages, | |
| 190 | + | pagination_range, | |
| 191 | + | showing_start, | |
| 192 | + | showing_end, | |
| 193 | + | }) | |
| 194 | + | } | |
| 195 | + | ||
| 155 | 196 | /// HTMX partial: library subscriptions tab. | |
| 156 | 197 | #[tracing::instrument(skip_all, name = "landing::library_tab_subscriptions")] | |
| 157 | 198 | pub(super) async fn library_tab_subscriptions( |
| @@ -40,6 +40,7 @@ pub fn public_routes() -> Router<AppState> { | |||
| 40 | 40 | .route("/library", get(landing::library)) | |
| 41 | 41 | .route("/cart", get(landing::cart_page)) | |
| 42 | 42 | .route("/library/tabs/purchases", get(landing::library_tab_purchases)) | |
| 43 | + | .route("/library/tabs/feed", get(landing::library_tab_feed)) | |
| 43 | 44 | .route("/library/tabs/subscriptions", get(landing::library_tab_subscriptions)) | |
| 44 | 45 | .route("/library/tabs/collections", get(landing::library_tab_collections)) | |
| 45 | 46 | .route("/library/tabs/wishlists", get(landing::library_tab_wishlists)) |
| @@ -163,6 +163,7 @@ impl_into_response!( | |||
| 163 | 163 | CollectionTemplate, | |
| 164 | 164 | // Library tabs | |
| 165 | 165 | LibraryPurchasesTabTemplate, | |
| 166 | + | LibraryFeedTabTemplate, | |
| 166 | 167 | LibrarySubscriptionsTabTemplate, | |
| 167 | 168 | LibraryCollectionsTabTemplate, | |
| 168 | 169 | LibraryWishlistsTabTemplate, |
| @@ -509,6 +509,19 @@ pub struct LibraryContactsTabTemplate { | |||
| 509 | 509 | pub total_buyer_contacts: usize, | |
| 510 | 510 | } | |
| 511 | 511 | ||
| 512 | + | /// Library feed tab (items from followed users, projects, and tags). | |
| 513 | + | #[derive(Template)] | |
| 514 | + | #[template(path = "partials/tabs/library_feed.html")] | |
| 515 | + | pub struct LibraryFeedTabTemplate { | |
| 516 | + | pub items: Vec<DiscoverItem>, | |
| 517 | + | pub total_items: u32, | |
| 518 | + | pub current_page: u32, | |
| 519 | + | pub total_pages: u32, | |
| 520 | + | pub pagination_range: Vec<u32>, | |
| 521 | + | pub showing_start: u32, | |
| 522 | + | pub showing_end: u32, | |
| 523 | + | } | |
| 524 | + | ||
| 512 | 525 | /// Library communities tab (Multithreaded forum memberships). | |
| 513 | 526 | #[derive(Template)] | |
| 514 | 527 | #[template(path = "partials/tabs/library_communities.html")] |
| @@ -24,6 +24,17 @@ | |||
| 24 | 24 | role="tab" | |
| 25 | 25 | aria-selected="false" | |
| 26 | 26 | aria-controls="tab-content" | |
| 27 | + | id="tab-feed" | |
| 28 | + | title="Updates from creators you follow" | |
| 29 | + | hx-get="/library/tabs/feed" | |
| 30 | + | hx-target="#tab-content" | |
| 31 | + | hx-swap="innerHTML" | |
| 32 | + | hx-indicator="#tab-spinner" | |
| 33 | + | onclick="setActiveTab(this)">Feed</button> | |
| 34 | + | <button class="tab" | |
| 35 | + | role="tab" | |
| 36 | + | aria-selected="false" | |
| 37 | + | aria-controls="tab-content" | |
| 27 | 38 | id="tab-subscriptions" | |
| 28 | 39 | hx-get="/library/tabs/subscriptions" | |
| 29 | 40 | hx-target="#tab-content" |
| @@ -16,11 +16,9 @@ | |||
| 16 | 16 | <a href="/discover">Discover</a> | |
| 17 | 17 | {% if let Some(user) = session_user %} | |
| 18 | 18 | <a href="/library">Library</a> | |
| 19 | - | <a href="/feed" title="Updates from creators you follow">Feed</a> | |
| 20 | 19 | <a href="/u/{{ user.username }}">Profile</a> | |
| 21 | 20 | <a href="/cart">Cart<span id="cart-badge" style="font-size: 0.8em;"></span></a> | |
| 22 | 21 | <a href="/dashboard">Dashboard</a> | |
| 23 | - | <a href="/changelog">Changelog</a> | |
| 24 | 22 | {% if user.is_admin %}<a href="/admin/waitlist">Admin</a>{% endif %} | |
| 25 | 23 | <form action="/logout" method="post" class="nav-form"> | |
| 26 | 24 | {% if let Some(token) = csrf_token %}<input type="hidden" name="_csrf" value="{{ token }}">{% endif %} | |
| @@ -33,7 +31,6 @@ | |||
| 33 | 31 | <a href="/login">Login</a> | |
| 34 | 32 | <a href="/join">Join</a> | |
| 35 | 33 | {% endif %} | |
| 36 | - | <button type="button" class="link-button shortcuts-help-btn" onclick="toggleShortcutsHelp()" aria-label="Keyboard shortcuts" title="Keyboard shortcuts (press ?)">?</button> | |
| 37 | 34 | </div> | |
| 38 | 35 | </nav> | |
| 39 | 36 | </header> |
| @@ -0,0 +1,76 @@ | |||
| 1 | + | <style> | |
| 2 | + | .feed-meta { font-size: 0.8rem; opacity: 0.6; margin-bottom: 0.75rem; } | |
| 3 | + | .feed-table-header { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: 0.5rem; padding: 0.5rem 0.75rem; background: var(--surface-alt); font-size: 0.75rem; opacity: 0.7; text-transform: uppercase; letter-spacing: 0.03em; } | |
| 4 | + | .feed-col-right { text-align: right; } | |
| 5 | + | .feed-results-table { border: 1px solid var(--border); border-top: none; } | |
| 6 | + | .feed-table-row { display: grid; grid-template-columns: 50px 1fr 100px 70px 70px; gap: 0.5rem; padding: 0.4rem 0.75rem; align-items: center; text-decoration: none; color: var(--detail); font-size: 0.85rem; border-bottom: 1px solid var(--border); transition: background 0.1s ease; } | |
| 7 | + | .feed-table-row:last-child { border-bottom: none; } | |
| 8 | + | .feed-table-row:nth-child(odd) { background: var(--light-background); } | |
| 9 | + | .feed-table-row:nth-child(even) { background: var(--surface-alt); } | |
| 10 | + | .feed-table-row:hover { background: var(--surface-muted); } | |
| 11 | + | .feed-item-name-cell { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; } | |
| 12 | + | .feed-item-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } | |
| 13 | + | .feed-item-creator { font-size: 0.75rem; opacity: 0.5; } | |
| 14 | + | .feed-pagination { display: flex; gap: 0.25rem; justify-content: center; margin-top: 1rem; } | |
| 15 | + | .feed-pagination a, .feed-pagination span { padding: 0.4rem 0.7rem; font-size: 0.85rem; text-decoration: none; color: var(--detail); border: 1px solid var(--border); } | |
| 16 | + | .feed-pagination span.current { background: var(--primary-dark); color: var(--primary-light); border-color: var(--primary-dark); } | |
| 17 | + | .feed-pagination a:hover { background: var(--surface-muted); } | |
| 18 | + | @media (max-width: 600px) { | |
| 19 | + | .feed-table-header, .feed-table-row { grid-template-columns: 1fr 70px; } | |
| 20 | + | .feed-table-header span:nth-child(1), .feed-table-row .badge:first-child, | |
| 21 | + | .feed-table-header span:nth-child(3), .feed-table-header span:nth-child(4), | |
| 22 | + | .feed-table-row span:nth-child(3), .feed-table-row span:nth-child(4) { display: none; } | |
| 23 | + | } | |
| 24 | + | </style> | |
| 25 | + | ||
| 26 | + | {% if items.is_empty() %} | |
| 27 | + | <div class="content-section"> | |
| 28 | + | <p class="muted">Nothing here yet.</p> | |
| 29 | + | <p>Follow users, projects, or tags to see their items here.</p> | |
| 30 | + | <p style="margin-top: 1rem;"> | |
| 31 | + | <a href="/discover" class="btn-primary" style="display: inline-block; text-decoration: none;">Browse Discover</a> | |
| 32 | + | </p> | |
| 33 | + | </div> | |
| 34 | + | {% else %} | |
| 35 | + | <div class="feed-meta">Showing {{ showing_start }}-{{ showing_end }} of {{ total_items }} items</div> | |
| 36 | + | ||
| 37 | + | <div class="feed-table-header"> | |
| 38 | + | <span>Type</span> | |
| 39 | + | <span>Name</span> | |
| 40 | + | <span>Tag</span> | |
| 41 | + | <span class="feed-col-right">Price</span> | |
| 42 | + | <span class="feed-col-right">Date</span> | |
| 43 | + | </div> | |
| 44 | + | <div class="feed-results-table"> | |
| 45 | + | {% for item in items %} | |
| 46 | + | <a href="/i/{{ item.id }}" class="feed-table-row"> | |
| 47 | + | <span class="badge">{{ item.item_type }}</span> | |
| 48 | + | <div class="feed-item-name-cell"> | |
| 49 | + | <span class="feed-item-name">{{ item.name }}</span> | |
| 50 | + | <span class="feed-item-creator">{{ item.creator }}</span> | |
| 51 | + | </div> | |
| 52 | + | <span>{{ item.primary_tag }}</span> | |
| 53 | + | <span class="feed-col-right">{% if item.is_free %}<span class="badge free">Free</span>{% else %}{{ item.price }}{% endif %}</span> | |
| 54 | + | <span class="feed-col-right">{{ item.date }}</span> | |
| 55 | + | </a> | |
| 56 | + | {% endfor %} | |
| 57 | + | </div> | |
| 58 | + | ||
| 59 | + | {% if total_pages > 1 %} | |
| 60 | + | <div class="feed-pagination"> | |
| 61 | + | {% if current_page > 1 %} | |
| 62 | + | <a href="#" hx-get="/library/tabs/feed?page={{ current_page - 1 }}" hx-target="#tab-content" hx-swap="innerHTML">«</a> | |
| 63 | + | {% endif %} | |
| 64 | + | {% for p in pagination_range %} | |
| 65 | + | {% if *p == current_page %} | |
| 66 | + | <span class="current">{{ p }}</span> | |
| 67 | + | {% else %} | |
| 68 | + | <a href="#" hx-get="/library/tabs/feed?page={{ p }}" hx-target="#tab-content" hx-swap="innerHTML">{{ p }}</a> | |
| 69 | + | {% endif %} | |
| 70 | + | {% endfor %} | |
| 71 | + | {% if current_page < total_pages %} | |
| 72 | + | <a href="#" hx-get="/library/tabs/feed?page={{ current_page + 1 }}" hx-target="#tab-content" hx-swap="innerHTML">»</a> | |
| 73 | + | {% endif %} | |
| 74 | + | </div> | |
| 75 | + | {% endif %} | |
| 76 | + | {% endif %} |
| @@ -223,10 +223,10 @@ async fn item_update_returns_json() { | |||
| 223 | 223 | let mut h = TestHarness::new().await; | |
| 224 | 224 | let item_id = h.create_creator_with_item("htmxuser", "audio", 500).await.item_id; | |
| 225 | 225 | ||
| 226 | - | // The update_item handler returns JSON (it does not check is_htmx_request) | |
| 226 | + | // The update_item handler returns JSON for non-HTMX requests | |
| 227 | 227 | let resp = h | |
| 228 | 228 | .client | |
| 229 | - | .htmx_put_form( | |
| 229 | + | .put_form( | |
| 230 | 230 | &format!("/api/items/{}", item_id), | |
| 231 | 231 | "title=Updated+Title", | |
| 232 | 232 | ) |
| @@ -280,6 +280,7 @@ async fn concurrent_promo_code_max_uses_one() { | |||
| 280 | 280 | // ============================================================================= | |
| 281 | 281 | ||
| 282 | 282 | #[tokio::test] | |
| 283 | + | #[cfg_attr(not(feature = "fast-tests"), ignore)] | |
| 283 | 284 | async fn concurrent_sandbox_per_ip_cap_holds() { | |
| 284 | 285 | let mut h = TestHarness::new().await; | |
| 285 | 286 |
| @@ -157,6 +157,7 @@ async fn sandbox_rss_returns_404() { | |||
| 157 | 157 | } | |
| 158 | 158 | ||
| 159 | 159 | #[tokio::test] | |
| 160 | + | #[cfg_attr(not(feature = "fast-tests"), ignore)] | |
| 160 | 161 | async fn sandbox_per_ip_cap() { | |
| 161 | 162 | let mut h = TestHarness::new().await; | |
| 162 | 163 |