Modernization update - #1076
Open
drusepth wants to merge 1142 commits into
Open
Modernization update#1076drusepth wants to merge 1142 commits into
drusepth wants to merge 1142 commits into
Conversation
drusepth
force-pushed
the
tailwind-redesign
branch
from
January 20, 2022 20:21
b1af55b to
9b3a51d
Compare
- Replace individual button event listeners with single delegated handler on gallery container for better performance and reliability - Consolidate delete and pin button handling into unified event system - Fix delete buttons to use data-method instead of deprecated UJS attributes - Remove template string syntax for better browser compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…to tailwind-redesign
Book model was missing the random_image_including_private method that other content types get from HasImageUploads. This caused errors on /my/content/recent when users had Books in their recent content. Added a stub method that returns the default book header image since Books don't have the image_uploads association. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The COUNT(*) GROUP BY query on content_change_events was taking 3.4s for users with many events, causing 4.1s login times. This adds a covering index on (user_id, content_type, content_id) that allows PostgreSQL to perform an index-only scan instead of scanning all matching rows for the GROUP BY. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The edit action was loading content without eager loading associations, causing N+1 queries when the view and serializer accessed user, universe, page_tags, image_uploads, and basil_commissions. The universe association is conditionally included since Universe model doesn't belong to another universe. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…g-XgrOQ Add universe filtering to books index page
…te-vpKga Use attribute selectors for sidebar stat updates
Add dark mode styles for .thredded--pagination-bottom to ensure pagination buttons have readable text (light on dark background) in dark mode. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…to tailwind-redesign
# Conflicts: # Gemfile.lock # app/controllers/basil_controller.rb # bin/webpack # config/environments/development.rb
…ource, pin Ruby to 3.2.3, add minimatch resolution
The card's overflow-hidden (only there to round the image corners) cut off the absolutely-positioned "Use only for" dropdown, so the caret next to Set as cover looked like it did nothing. Round the media area directly instead, and anchor the menu to the right edge so it stays within the card. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
…ebook into tailwind-redesign
The uniqueness validation can't stop two concurrent autosaves that both pass validation before either inserts, so duplicate live values could still be created. Add a partial unique index on (attribute_field_id, entity_id, entity_type) WHERE deleted_at IS NULL, built concurrently on PostgreSQL. If a previous concurrent build failed and left an invalid index, the migration drops it and retries, and it lifts statement_timeout for the build. save_attribute_value! now also retries once on RecordNotUnique, so a save rejected by the index updates the row the other request created. Existing duplicates must be soft-deleted before migrating. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kAEAmpDpY99o7Xp9rABr1
The green "Public" pill showed on every image even when the page itself was private, which read as "strangers can see this". Remove it and make the image editor's "Who can see this image" dropdown the single control. Private images now get a small lock chip on the thumbnail instead. Private uploads are skipped when picking a cover for other viewers, so a private image set as cover (or as a per-shape cover) on a public page silently fell back to a different image. Show a short warning on the card in that case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
The image-helper cleanup removed the preview_image/image_type/image_data locals from universes/content_list, but the bottom 'Explore the Universe' CTA still referenced them, so every /plan/universes/:id/<type> page raised a 500. Render that background through content_image_tag like the hero does; its placeholder is the same card-headers/universes.webp. Adds a request test covering the page for visitors and owners. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AeuXWiK6BbUXzDmw5CKp9x
…ebook into tailwind-redesign
The autosave script could send a second save for a field while the first was still in flight (idle-timer save followed by blur), which is what created duplicate attribute rows. It also tracked unsaved changes with a single flag keyed by element id, and every text field on the edit page shares the id "field_value", so a save finishing in one field could mark text typed in another field (or typed during the request) as saved, and that text was never sent. Now each form has its own state: at most one request is in flight per form, edits made meanwhile go out in one follow-up request, and whether a form needs saving is decided by comparing its serialized data with what was last sent. A failed save leaves the form unsaved so the next blur retries it, and anything unsaved when the page is closed is sent with navigator.sendBeacon. autosave:start and autosave:error are now dispatched alongside autosave:success, bubbling from the field, so the existing listeners for them (and the field-level listeners on the foldered index page) start working. Removed handling for .js-save-indicator, which no view renders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kAEAmpDpY99o7Xp9rABr1
Image work in the gallery didn't mark the page as recently edited (only cover toggles touched it) and never showed up in the page's changelog, which only knew about Attribute changes. Add GalleryActivity, called from every gallery endpoint: - touch! bumps the page's updated_at (used alone for reorders) - record! also writes a page-level ContentChangeEvent (image_added, image_removed, image_updated, cover_changed) with a small image snapshot. Edits/cover changes to the same image by the same user within 10 minutes merge into one event; changes that cancel out remove it. Basil images count once they're saved to the page, not when generation finishes. The changelog page, its stats and the sidebar count now include these events, rendered by a new content/changelog/_image_change partial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
…ebook into tailwind-redesign
content/edit/_edit_tools.html.erb isn't rendered anywhere (no static or dynamic render resolves to it), and nothing outside it references the functions or element ids it defines. Its saveAllFields() also looked up a #save-all-btn that no view renders and only blurred fields, which never saved unfocused ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kAEAmpDpY99o7Xp9rABr1
Production Sidekiq failed GenerateImageCropsJob with "uninitialized constant ImageUpload::FRAMED_STYLES", so reframed images never got new banner/card/square derivatives and the site kept serving the old cut. Read the style list straight from ImagePresets (where the constant came from) and drop the now-unused model constant. Also refuse to report success when the attachment lacks a requested style: Paperclip silently skips unknown styles, which would otherwise leave the old cut in place with no error. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
Cancelled commissions are hard-deleted so they don't count toward the free image limit (which counts soft-deleted rows) or Basil stats. The generation job now skips commissions that were cancelled while queued or mid-generation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RZ2cmCvobYuZQ5rMmbgbdf
…ebook into tailwind-redesign
Shows how much the whole Notebook.ai community writes: live words and writers today, an all-time total, daily and monthly charts, words by category (documents, characters, locations, items, ...), a year-long heatmap, records and weekday rhythms, and what was created this month. Every number is an anonymous aggregate. CommunityStatsService computes word-count deltas in SQL with LAG() so a year of history is a few queries (works on Postgres and SQLite), and caches history for hours and today's numbers for minutes. Adds a for_date index on word_count_updates, a warm-up job/rake task, and a footer link. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
…ebook into tailwind-redesign
It collided with 20260927120000_add_unique_live_value_index_to_attributes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
With ~50M word_count_updates rows, aggregating on page load (or even every few hours) is too expensive. Instead: * CommunityStatsRollup computes one day at a time: only that day's word_count_updates rows (for_date index) with an indexed backwards probe for each row's previous count. Page-creation and sign-up counts binary search the primary key instead of scanning tables without created_at indexes. * Results land in community_daily_stats (~50 rows/day) and community_monthly_writers; CommunityStatsService only reads those. * CommunityStatsRefreshJob / rake community:refresh re-roll the last 3 days every 15 minutes; the page enqueues one refresh when numbers are stale. rake community:backfill fills history once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
EndOfDayAnalyticsReport already records pages created per type, documents, timelines, sign-ups and forum posts for every past day, so /community now reads those instead of counting them again. This removes the primary-key binary search from CommunityStatsRollup, which now only rolls up word counts. * EOD reports gain writing_goals_completed. * EndOfDayAnalyticsJob finalizes yesterday's community word counts. * The admin hub's words today/this week read the precomputed community stats instead of querying once per edited page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
…query Postgres inlined the delta subquery into both the filter and the SUM, doubling the index probes per row; an OFFSET 0 fence keeps it computed once (about 30% faster on a 5M-row test table). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
Basil images were saved with S3's hex ETag as their ActiveStorage checksum. ActiveStorage expects a base64 MD5 and verifies it when it downloads the original to build a variant, so every resized or cropped Basil image (gallery, show page, covers) raised IntegrityError and rendered broken, while links to the original still worked. - GenerateBasilImageJob computes the checksum from the image bytes - BasilController#complete converts the ETag via BasilCommission.checksum_from_etag - FixBasilBlobChecksums migration rewrites existing hex checksums on amazon_basil blobs (idempotent; other services untouched) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
Crops are stored as fractions and turned into pixels using the image's width/height. Basil commissions never stored a size and relied on ActiveStorage's background analysis, which failed on the same bad checksum, so every Basil crop was silently ignored (plain centre crop). - GenerateBasilImageJob records width/height from the PNG header - gallery:backfill_basil_dimensions fills in existing Basil images on pages (analysing the blob when needed); run after the FixBasilBlobChecksums migration Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
The characters sidebar on books#show called custom_public_thumbnail_url, which no longer exists after the cover image refactor, raising NoMethodError. Render the thumbnail through content_image_tag's small square preset instead, and preload image uploads for the listed characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VwqJRU7Fc7LBqpYi5U8S6e
PageCollection has a plain cover_image string column, so calling cover_image(include_private:, pick:, preset:) on it raised "wrong number of arguments (given 1, expected 0)" when a collection was shared to the stream (ContentPageSharesController#show, stream feed). Only call the gallery cover_image API on HasImageUploads models, and render a non-gallery page's own header_image_url when it has one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LE1YSYoCW4tbGUb9s68iCf
…ebook into tailwind-redesign
All three places that attach Basil images now go through BasilCommission.create_png_blob!, which takes the checksum (base64 MD5), content type (image/png) and pixel size from the PNG itself: - GenerateBasilImageJob (current generation path) - BasilController#complete_commission and the old data_migrations:attach_s3_images_to_basil_commissions task, via attach_stored_png!. These used S3's ETag as the checksum (hex, and not an MD5 at all for multipart uploads) and S3's binary/octet-stream content type, which ActiveStorage won't build variants from. The blob is marked identified/analysed with its size, which also saves two S3 downloads per image. FixBasilBlobContentTypes repairs existing Basil blobs stored as binary/octet-stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rrKNqLURkxbJwi4UY83iT
PageCollection's legacy cover_image column shadows the gallery API (HasImageUploads#cover_image), so views that duck-typed on respond_to?(:cover_image?) could call it with options and crash. - Add content_cover_image / content_cover_image? helpers that only use the gallery API on HasImageUploads records, and replace every direct cover_image / cover_image? call in views and controllers with them. - Add PageCollection#custom_header_image? and read the legacy column only inside the model; collection cards now use header_image_url, so uploaded header images show on profile and explore pages too. - Preload header image attachments where collections are listed. - Collapse duplicated placeholder branches in the timeline card views. - Add a regression test for showing a shared collection, plus helper and model tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LE1YSYoCW4tbGUb9s68iCf
…ebook into tailwind-redesign
The page only enqueues its own refresh once numbers are 90 minutes old, so it doesn't compete with the hourly cron. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
* "Worlds taking shape" now shows every page worked on in the month (distinct pages with a word_count_updates row; timeline events count as their timelines), with EOD created counts shown as "N new". Rolled up nightly by the EOD job and by community:backfill; ~1s for a month of ~1M rows on Postgres. * Hero: flat notebook-blue background, no "live" tag, and the headline "Everything is private by default. But none of us write alone." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
…notebook-blue CTA Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QxnmADXqC8Hsh9J1oVYYm5
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
And so it begins...
To-do items to track: