Skip to content

Modernization update - #1076

Open
drusepth wants to merge 1095 commits into
masterfrom
tailwind-redesign
Open

drusepth wants to merge 1095 commits into
masterfrom
tailwind-redesign

Conversation

@drusepth

@drusepth drusepth commented Jan 7, 2022

Copy link
Copy Markdown
Member
  • MaterializeCSS hasn't been updated in almost 2 years and is full of bugs
  • TailwindCSS is pretty nice and hot right now

And so it begins...

To-do items to track:

  • Check referrals working on both new sign up forms
  • Go back and style everything for dark mode again (oof)
  • Style everything for mobile also

@drusepth drusepth self-assigned this Jan 7, 2022
@drusepth drusepth changed the title Tailwind redesign Modernization update Feb 14, 2023
drusepth and others added 23 commits February 21, 2026 23:08
The /about/paper page was making ~66 database queries due to calling
content_type.last for each of 31 content types in a loop. This change:

- Adds GreenService.max_ids_for_content_types to batch-fetch all max IDs upfront
- Refactors total_physical_pages_equivalent to accept optional pre-fetched max_id
- Consolidates Document queries from 3 to 1 using conditional aggregation
- Uses cached IDs throughout the paper action loop

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /about/paper page was crashing because Book is in content_type_names[:all]
(used in the paper.html.erb view loop) but routes are generated from
content_types[:all], which doesn't include Book. Added a standalone route
and controller action for Book, similar to how Timeline is handled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace loop of ~33 individual MAX(id) queries with a single UNION ALL
query that fetches all max IDs in one database roundtrip. Also exclude
Books from the Community Contribution Breakdown since they are just
collections of Documents (avoiding double-counting).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The attribute_categories method was eager loading ALL attribute values
for ALL entities belonging to a user. For a user with many entities,
this caused queries taking 2+ seconds.

ContentSerializer already fetches attribute values separately with a
scoped query (Attribute.where(entity_id: content.id)), so the
eager-loaded values were never used.

Changed eager_load(attribute_fields: :attribute_values) to
eager_load(:attribute_fields) to keep field loading but eliminate
the expensive values query.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tooltip-bottom class and data-tooltip attributes to the Card View
and List View toggle buttons so users can see what each icon does.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Transform circular notification bell and user avatar buttons into
book-shaped (3:4 aspect ratio) buttons to match the notebook theme.
Also increase search bar height and standardize borders across all
navbar elements for visual consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add eager loading for timeline_events and precompute statistics in the
controller instead of computing them inline in the view. This reduces
queries from ~2N+1 to 2-3 for pages with N timelines.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Completely redesign /worldbuilding/books with 13 sections showcasing
the full power of the Books feature: analytics, writing goals,
worldbuilding integration, document analysis, status workflow, and more.

Add "Complete Your Writing Toolkit" cross-sell section to both Books
and Timeline pages, displaying Timelines -> Documents -> Books with
equal-height cards and "You are here" indicator.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use chartjs-plugin-annotation to draw a dashed line at the daily goal
threshold instead of rendering goals as columns. This fixes the issue
where Chartkick rendered both datasets as columns despite the mixed
chart type configuration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add a new marketing/information page at /worldbuilding/documents to showcase
the Documents feature, following the pattern used for Books and Timelines.

The page includes sections for:
- Hero with "Capture Ideas. Craft Stories." tagline
- Value proposition highlighting connected writing
- Core features (folder organization, status workflow, free tier)
- Writing environment features (autosave, word count, streaks)
- 8-step status workflow visualization
- Worldbuilding integration for linking characters/locations/items
- AI-powered document analysis (premium feature)
- Revision history and export options
- Additional features (universe focus, collaboration, dark mode, privacy)
- Platform stats and trust messaging
- Related writing tools navigation (Books, Documents, Timelines)
- Content type icon grid

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use lightweight StringScanner-based counter for documents over 300KB
to avoid ~28MB peak memory from WordCountAnalyzer gem's string
transformations. Jobs now use count_with_fallback for reliability.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reorganize the 13 event types into story structure phases (Beginning,
Rising Action, Climax & Turning Points, Resolution) with visual
section dividers. Add improved card design with left border accents,
circular icon containers, and hover effects. Highlight the Climax
card with a gradient background.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change Timelines and Documents links in the "Complete Your Writing
Toolkit" section to point to worldbuilding marketing pages instead
of the app pages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reduces database queries per dashboard load from ~770 to ~15-20 by:
- Adding composite index on (user_id, for_date) for word_count_updates
- Replacing find_by loops with bulk queries using sanitize_sql_array
- Adding batch_words_written_on_dates for efficient multi-date lookups
- Adding dates_with_writing_activity for streak calculation
- Refactoring MainController to use batch methods instead of per-date calls

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The navbar search bar and icons extended too close to the edge when
pages have right sidebars. Increased right padding from pr-2 to
pr-4 lg:pr-6 for better visual alignment with content below.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…page

- Add manual route for Timeline (not in content_types[:all]) alongside Books and Documents
- Add Writing Tools section to help center with links to Timelines, Documents, and Books guides

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Books, Documents, and Timelines are writing tools rather than worldbuilding
content types, so they now live under /writing/ instead of /worldbuilding/:
- /writing/books
- /writing/documents
- /writing/timelines

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add hero section with notebook-blue background, help icon, and welcoming headline
- Add meta description for SEO
- Remove back arrow header (not useful for SEO visitors)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…-width

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add three new help guides addressing gaps identified in audit:
- Managing Your Account: profile, security, notifications, billing
- Your Data: Data Vault, export formats, recycle bin, archives, storage
- Troubleshooting & FAQ: common issues for universes, content, collaboration

Add Quick Links section to Help Center index linking to Data Vault,
Billing FAQ, Privacy Policy, Forums, and Referrals.

Redesign Billing FAQ page from Materialize to Tailwind CSS with
collapsible details elements and consistent styling.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move the timelines user route definition earlier in routes.rb to ensure
the route helper is available before Thredded engine mounts and captures
the main_app helpers. This fixes a timing issue where the route helper
could be missing during boot.

Also add defensive error handling in the badge partial to gracefully
skip the favorite link if the route helper is unavailable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
claude and others added 30 commits August 20, 2026 17:45
The prompts smart sidebar re-fetches the persisted page for the cached
ContentPage it renders. That lookup returns nil when the page has been
deleted or archived since the cached list was built, and ContentSerializer
then blew up on nil in production (undefined method `user' for nil).

Skip rendering the quick-reference sidebar when the page can no longer be
found, and raise a clear ArgumentError from ContentSerializer when it is
handed nil so future callers fail with an explanation instead of a
NoMethodError deep in initialize.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017knvD2VVk3RwE5gtMQNNME
…ethod-3uvyhf

Fix subscription history invoice display and plan name handling
The billing row in the Tailwind navbar's account dropdown emitted a
hardcoded "PREMIUM" badge for every logged-in user, so users on the
free plan (selected_billing_plan_id = 1) saw it too.

Gate the label and its color on User#on_premium_plan?, matching the
behavior of the Materialize sidenav badge, and show "STARTER" in gray
for users who aren't on a premium plan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxqvazvvQw1MDnXVTDyhf2
…rd-user-2j1ec2

Show subscription plan status in navbar billing link
Add tabindex="-1" to the "Markdown formatting help" link so tabbing from
the post content textarea goes straight to the submit button, letting
users tab+enter to submit. The link stays clickable for mouse users.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KgELkknCnf32kJgVqNZVxh
Archiving a field in the template editor sets attribute_fields.hidden,
and the editor tells the user the field "appears in your template and
content pages" while it's active. The older page views honored that
(app/views/content/form/_panel.html.erb still skips hidden fields), but
the Tailwind show/edit views render whatever ContentSerializer hands
them, and the serializer loaded every field in a category regardless of
its hidden flag. Archived fields with answers therefore reappeared on
pages after the redesign, leaving deleting the field as the only way to
get rid of them.

Filter hidden fields out of ContentSerializer so every surface built on
it -- show, edit, quick reference, changelog, the smart sidebar, and the
category completion percentages -- drops them together. Answers are left
untouched, so restoring a field in the template editor brings its
content straight back. Name fields are kept even if hidden, since pages
are built around having one and the editor never offers to archive them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7JcoLsyP2qYbBsDipRKVT
…p-x8tww5

Remove markdown help link from tab order
…ility-93t4pj

Keep archived fields off content pages
…m-him8td

Keep page sidebars from sliding under the site nav rail on tablet widths
…al9msk

Fix document headers rendering as normal text
…-visibility-ud1jak

Add comprehensive authorization tests for Book privacy
…th-yazvri

Hide post actions menu for logged-out users
…riptions-p0cs3l

Fix parallel Stripe subscriptions and improve subscription sync
…screens-9k19e1

Fix dropdown menu alignment in content index
…ule86

Handle deleted/archived pages in smart sidebar gracefully
The field archive toggle relied on the browser's native title attribute,
which renders unstyled and only after a long hover delay. Switch it to the
existing CSS tooltip system (tooltip-left + data-tooltip) already used by
the category card's archive and edit buttons, so it appears instantly and
matches the rest of the template editor.

Also stop the archive JS from writing title back onto the buttons after a
toggle (which would have re-introduced the native tooltip on the category
button, stacked on top of its styled one), and add an aria-label so the
icon-only button keeps an accessible name now that title is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JuwsMm7Vmw8o1sPUHR4QPT
Documents the current state of the content#edit gallery tab (bugs, image
display contexts, cover selection) and a phased plan covering panel
polish, no-reload uploads, a per-image focal point / crop editor, and a
single site-wide image renderer.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
…ploads

Phase 1 and 2 of the gallery UX plan (docs/gallery_ux_plan.md).

- Add ContentImage presenter wrapping ImageUpload and BasilCommission so
  views and JSON responses treat every gallery image the same way.
- Add ImageUploadAuthorizer / BasilCommissionAuthorizer (via a shared
  ContentImageAuthorization rule) and use them for pin, sort, notes,
  privacy and delete, so anyone who can edit a page can manage its images.
- Replace the two duplicated card branches with one _card partial:
  cover chip on the image, a labelled Set-as-cover button, a real circular
  drag handle that also works on touch, move up/down buttons, inline
  two-step delete, per-image privacy toggle, dimensions/size meta, notes
  that save only when changed, larger (600px) thumbnails, lazy loading.
- Move the inline gallery script into a Stimulus gallery controller and
  drop the document-wide MutationObserver.
- Add ImageUploadService (quota charge/refund) and a JSON
  POST /image_uploads endpoint; the gallery now uploads through a drop
  zone with per-file progress, clipboard paste and retry, and appends the
  rendered card without reloading. The old cocoon form remains as the
  no-JS fallback.
- Delete credits bandwidth back to the uploader and returns the new
  remaining quota; the sidebar count and empty state update live.
- Add cropperjs dependency for the upcoming image editor.
- Tests for the presenter, service and upload/delete/privacy endpoints.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
Phases 3, 3b and 4 of docs/gallery_ux_plan.md.

Framing data
- Migration adds crops (JSON, normalised rectangles per shape), focal_x/y,
  width/height and crops_generated_at to image_uploads and basil_commissions.
- ImagePresets defines the three display shapes used across the site:
  banner 3:1 (1500x500), card 3:2 (900x600), square 1:1 (600x600).
- HasImageFraming (both models) validates crops against the preset ratio,
  computes the automatic crop around the focal point, and exposes the
  CSS object-position fallback. ImageUpload captures oriented pixel
  dimensions on upload.

Editor
- Cropper.js based modal on the gallery tab (image_editor_controller.js):
  shape tabs with auto/custom state, ratio-locked crop box with pan and
  zoom, rule-of-thirds grid, focal point mode, live previews rendered in
  the real hero/card/thumbnail chrome, notes and privacy, cover toggle,
  keyboard shortcuts, #gallery/<image> deep links. The modal lives on
  <body> so the transformed page layout cannot clip it.
- PATCH endpoints for uploads and Basil images accept crops, focal point,
  notes and privacy.

Derivatives
- Paperclip cropper processor cuts banner/card/square styles from the
  stored rectangle (or the focal-point auto crop) before resizing.
- ImageDerivativeService regenerates upload styles (keeping the stored
  filename so the original is never orphaned) and builds cropped
  ActiveStorage variants for Basil images. GenerateImageCropsJob runs
  after framing changes; rake gallery:backfill_* tasks cover old uploads.

Rendering
- HasImageUploads#cover_image picks the pinned image (else first/random)
  as a ContentImage; content_image_tag(content, :card) serves the preset
  derivative, or the largest general size with the focal point as
  object-position until derivatives exist, or the type placeholder.
- Hero header, content cards, card index, context sidenav, square thumbs,
  Open Graph tags and the remaining common call sites use the helper.
  The hero keeps a 3:1 shape from the md breakpoint (aspect-banner).

Also: sequential client-side uploads, quota charge and store in one
transaction, Paperclip test files isolated under tmp/, tests for
framing, derivatives, helper and endpoints.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
…ghtbox, mobile uploads, a11y, N+1 and legacy cleanup

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
Round two, steps 1 to 3 of docs/gallery_ux_plan.md section 7.

- cover_for on both image tables: an image can be the cover for specific
  shapes (banner, card, thumbnail, link preview) while the pin stays the
  default; ContentCoverService keeps one holder per shape per page and
  toggle_image_pin accepts an optional preset param.
- Card and editor get a split cover button with a per-shape menu, chips on
  the image, and "Used now" labels under each editor preview.
- New social preset (40:21, 1200x630) with its own style, editor tab and
  link-card preview; Open Graph / Twitter tags use it with
  summary_large_image and explicit dimensions (content_social_meta).
- Shape derivatives are now WebP (quality 82, stripped), with reduced
  banner_sm (750x250) and square_sm (200x200) sizes and a 1600px xlarge
  WebP for the lightbox and editor. Basil variants request WebP too.
- content_image_tag emits width/height, decoding=async, a banner srcset
  with sizes, and a size: :small option used on the tiny thumbnail boxes.
- S3 objects get a one-year Cache-Control in production; the backfill task
  now also produces xlarge for existing uploads.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
- content/show/_gallery_content renders through ContentImage.gallery_for
  (same order and privacy rules as the editor), serves the large
  derivative in the grid with the focal point, shows notes as captions
  and the cover chip, and no longer uses inline onclick handlers.
- New lightbox Stimulus controller: keyboard (arrows, Home/End, Esc),
  swipe on touch, focus trap and return, "3 of 8" counter, captions,
  open-original link and, for editors, an "Edit framing" deep link.
  Shows the 1600px WebP instead of the original.
- Old global modal functions and the gallery CSS block removed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
- ImageUploadService converts HEIC/HEIF uploads (iPhone photos) to JPEG
  with ImageMagick before storing, so every derivative and the editor work
  in any browser.
- The drop zone accepts .heic/.heif, adds a "Take a photo" button on touch
  devices (capture=environment), and an off-by-default "Shrink large photos
  before uploading" toggle (remembered per browser) that resizes JPEG/PNG/
  WebP photos over 2500px in the browser and reports the saving per file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
- Editor: focus trap, roving tabindex with arrow-key navigation between
  shape tabs, initial focus on the active tab, a live region announcing
  shape and framing changes, a keyboard shortcuts sheet (?), a focusable
  focal point, and contrast fixes on the auto/custom chip and help text.
- Gallery cards: privacy button carries its state in aria-label and
  aria-pressed; toasts announce with role=status.
- Cropper handles grow to 20px on touch devices; transitions respect
  prefers-reduced-motion.
- Lightbox events are bound by hand so they survive moving the dialog to
  <body> (arrow keys and buttons now work), and the show page is audited.

Verified with axe-core (wcag2a/aa, wcag21aa, best-practice) on the gallery
tab, the open editor and the lightbox in both light and dark mode: zero
violations; keyboard traversal stays inside the dialogs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
Round two, steps 7 and 8.

N+1 cleanup
- ApplicationController#preload_cover_images loads image_uploads and
  basil_commissions (with blobs) for a whole list in a fixed number of
  queries, resetting stale associations first. Generic ContentPage rows
  from User#content (the dashboard, content index, Basil index, recent
  content) now carry the same cover API through page_type + id and a
  bulk preload path.
- The hand-built image pool caches in the content, users and browse
  controllers are gone; their views render through content_image_tag.
- Added a request test asserting a flat image query count on the
  character index and the dashboard.

Retired paths
- Every remaining caller of random_image_including_private,
  first_public_image, pinned_or_random_image_including_private,
  custom_thumbnail_url, custom_public_thumbnail_url, get_preview_image
  and combine_and_sort_gallery_images now uses cover_image /
  cover_image_url / content_image_tag (about 50 call sites, including
  RSS, which keeps the legacy JPEG sizes for feed readers).
- HasImageUploads is reduced to cover_image, cover_image?,
  cover_image_url, clear_cover_image_cache and header_asset_for;
  ContentPage's duplicate overrides and ApplicationHelper's gallery
  helpers are deleted. PageCollection's own header image is
  header_image_url.
- Dead partials (content/_form, content/form/_panel and gallery,
  form/images/_gallery and _edit_list, field_types/_migration_link,
  content/list/_cards, display/_image_card_header) and the old
  image_uploads.js are removed, along with their tests.
- A lint test fails the build if any retired helper name reappears
  under app/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZJAZpUXf5my4TEFX7NqBA
Lists the characters connected to a book underneath Book Details, using
the characters that document analysis linked to the book's chapters and
falling back to the book's universe roster when no chapters have been
analyzed yet.

Each entry links to the character page and shows its thumbnail (or the
Character icon) plus its role. The list is capped at twelve entries with
a "+N more" summary, and every character is run through can_read? so
private pages never leak onto a publicly viewable book.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pi5eSNA1PW88BGQMRGJM7E
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants