Showing preview only (2,442K chars total). Download the full file or copy to clipboard to get everything.
Repository: basecamp/fizzy
Branch: main
Commit: bcf6e9213199
Files: 1525
Total size: 2.1 MB
Directory structure:
gitextract_3xz67y80/
├── .claude/
│ └── CLAUDE.md
├── .dockerignore
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── config.yml
│ │ └── preapproved.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci-checks.yml
│ ├── ci-oss.yml
│ ├── ci-saas.yml
│ ├── dependabot-sync-saas-lockfile.yml
│ ├── publish-image.yml
│ └── test.yml
├── .gitignore
├── .gitleaks.toml
├── .gitleaksignore
├── .mise.toml
├── .rubocop.yml
├── .ruby-version
├── AGENTS.md
├── CONTRIBUTING.md
├── Dockerfile
├── Gemfile
├── Gemfile.saas
├── LICENSE.md
├── README.md
├── Rakefile
├── STYLE.md
├── app/
│ ├── assets/
│ │ ├── images/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ ├── _global.css
│ │ ├── access-tokens.css
│ │ ├── android.css
│ │ ├── animation.css
│ │ ├── attachments.css
│ │ ├── autoresize.css
│ │ ├── avatars.css
│ │ ├── bar.css
│ │ ├── base.css
│ │ ├── blank-slates.css
│ │ ├── bubble.css
│ │ ├── buttons.css
│ │ ├── card-columns.css
│ │ ├── card-perma.css
│ │ ├── cards.css
│ │ ├── circled-text.css
│ │ ├── color-picker.css
│ │ ├── comments.css
│ │ ├── credentials.css
│ │ ├── dialog.css
│ │ ├── dividers.css
│ │ ├── drag_and_drop.css
│ │ ├── events.css
│ │ ├── expandable.css
│ │ ├── filters.css
│ │ ├── flash.css
│ │ ├── font-face.css
│ │ ├── golden-effect.css
│ │ ├── header.css
│ │ ├── icons.css
│ │ ├── import.css
│ │ ├── inputs.css
│ │ ├── ios.css
│ │ ├── knobs.css
│ │ ├── layout.css
│ │ ├── lexxy.css
│ │ ├── lightbox.css
│ │ ├── markdown.css
│ │ ├── native.css
│ │ ├── nav.css
│ │ ├── notifications.css
│ │ ├── pagination.css
│ │ ├── panels.css
│ │ ├── performance-notice.css
│ │ ├── pins.css
│ │ ├── popup.css
│ │ ├── print.css
│ │ ├── pwa.css
│ │ ├── qr-codes.css
│ │ ├── reactions.css
│ │ ├── reset.css
│ │ ├── search.css
│ │ ├── separators.css
│ │ ├── settings.css
│ │ ├── spinners.css
│ │ ├── steps.css
│ │ ├── syntax.css
│ │ ├── theme-switcher.css
│ │ ├── toggles.css
│ │ ├── tooltips.css
│ │ ├── trays.css
│ │ ├── user.css
│ │ ├── utilities.css
│ │ └── welcome-letter.css
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection.rb
│ ├── controllers/
│ │ ├── account/
│ │ │ ├── cancellations_controller.rb
│ │ │ ├── entropies_controller.rb
│ │ │ ├── exports_controller.rb
│ │ │ ├── imports_controller.rb
│ │ │ ├── join_codes_controller.rb
│ │ │ └── settings_controller.rb
│ │ ├── admin_controller.rb
│ │ ├── application_controller.rb
│ │ ├── boards/
│ │ │ ├── columns/
│ │ │ │ ├── closeds_controller.rb
│ │ │ │ ├── not_nows_controller.rb
│ │ │ │ └── streams_controller.rb
│ │ │ ├── columns_controller.rb
│ │ │ ├── entropies_controller.rb
│ │ │ ├── involvements_controller.rb
│ │ │ └── publications_controller.rb
│ │ ├── boards_controller.rb
│ │ ├── cards/
│ │ │ ├── assignments_controller.rb
│ │ │ ├── boards_controller.rb
│ │ │ ├── closures_controller.rb
│ │ │ ├── columns_controller.rb
│ │ │ ├── comments/
│ │ │ │ └── reactions_controller.rb
│ │ │ ├── comments_controller.rb
│ │ │ ├── drafts_controller.rb
│ │ │ ├── goldnesses_controller.rb
│ │ │ ├── images_controller.rb
│ │ │ ├── not_nows_controller.rb
│ │ │ ├── pins_controller.rb
│ │ │ ├── previews_controller.rb
│ │ │ ├── publishes_controller.rb
│ │ │ ├── reactions_controller.rb
│ │ │ ├── readings_controller.rb
│ │ │ ├── self_assignments_controller.rb
│ │ │ ├── steps_controller.rb
│ │ │ ├── taggings_controller.rb
│ │ │ ├── triages_controller.rb
│ │ │ └── watches_controller.rb
│ │ ├── cards_controller.rb
│ │ ├── client_configurations_controller.rb
│ │ ├── columns/
│ │ │ ├── cards/
│ │ │ │ └── drops/
│ │ │ │ ├── closures_controller.rb
│ │ │ │ ├── columns_controller.rb
│ │ │ │ ├── not_nows_controller.rb
│ │ │ │ └── streams_controller.rb
│ │ │ ├── left_positions_controller.rb
│ │ │ └── right_positions_controller.rb
│ │ ├── concerns/
│ │ │ ├── authentication/
│ │ │ │ └── via_magic_link.rb
│ │ │ ├── authentication.rb
│ │ │ ├── authorization.rb
│ │ │ ├── block_search_engine_indexing.rb
│ │ │ ├── board_scoped.rb
│ │ │ ├── card_scoped.rb
│ │ │ ├── column_scoped.rb
│ │ │ ├── current_request.rb
│ │ │ ├── current_timezone.rb
│ │ │ ├── day_timelines_scoped.rb
│ │ │ ├── filter_scoped.rb
│ │ │ ├── request_forgery_protection.rb
│ │ │ ├── routing_headers.rb
│ │ │ ├── set_platform.rb
│ │ │ ├── turbo_flash.rb
│ │ │ └── view_transitions.rb
│ │ ├── events/
│ │ │ ├── day_timeline/
│ │ │ │ └── columns_controller.rb
│ │ │ └── days_controller.rb
│ │ ├── events_controller.rb
│ │ ├── filters/
│ │ │ └── settings_refreshes_controller.rb
│ │ ├── filters_controller.rb
│ │ ├── join_codes_controller.rb
│ │ ├── landings_controller.rb
│ │ ├── my/
│ │ │ ├── access_tokens_controller.rb
│ │ │ ├── identities_controller.rb
│ │ │ ├── menus_controller.rb
│ │ │ ├── passkey_challenges_controller.rb
│ │ │ ├── passkeys_controller.rb
│ │ │ ├── pins_controller.rb
│ │ │ └── timezones_controller.rb
│ │ ├── notifications/
│ │ │ ├── bulk_readings_controller.rb
│ │ │ ├── readings_controller.rb
│ │ │ ├── settings_controller.rb
│ │ │ ├── trays_controller.rb
│ │ │ └── unsubscribes_controller.rb
│ │ ├── notifications_controller.rb
│ │ ├── prompts/
│ │ │ ├── boards/
│ │ │ │ └── users_controller.rb
│ │ │ ├── cards_controller.rb
│ │ │ ├── tags_controller.rb
│ │ │ └── users_controller.rb
│ │ ├── public/
│ │ │ ├── base_controller.rb
│ │ │ ├── boards/
│ │ │ │ ├── columns/
│ │ │ │ │ ├── closeds_controller.rb
│ │ │ │ │ ├── not_nows_controller.rb
│ │ │ │ │ └── streams_controller.rb
│ │ │ │ └── columns_controller.rb
│ │ │ ├── boards_controller.rb
│ │ │ └── cards_controller.rb
│ │ ├── pwa_controller.rb
│ │ ├── qr_codes_controller.rb
│ │ ├── searches/
│ │ │ └── queries_controller.rb
│ │ ├── searches_controller.rb
│ │ ├── sessions/
│ │ │ ├── magic_links_controller.rb
│ │ │ ├── menus_controller.rb
│ │ │ ├── passkeys_controller.rb
│ │ │ └── transfers_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── signups/
│ │ │ └── completions_controller.rb
│ │ ├── signups_controller.rb
│ │ ├── tags_controller.rb
│ │ ├── users/
│ │ │ ├── avatars_controller.rb
│ │ │ ├── data_exports_controller.rb
│ │ │ ├── email_addresses/
│ │ │ │ └── confirmations_controller.rb
│ │ │ ├── email_addresses_controller.rb
│ │ │ ├── events_controller.rb
│ │ │ ├── joins_controller.rb
│ │ │ ├── push_subscriptions_controller.rb
│ │ │ ├── roles_controller.rb
│ │ │ └── verifications_controller.rb
│ │ ├── users_controller.rb
│ │ ├── webhooks/
│ │ │ └── activations_controller.rb
│ │ └── webhooks_controller.rb
│ ├── helpers/
│ │ ├── accesses_helper.rb
│ │ ├── application_helper.rb
│ │ ├── avatars_helper.rb
│ │ ├── boards_helper.rb
│ │ ├── bridge_helper.rb
│ │ ├── cards_helper.rb
│ │ ├── clipboard_helper.rb
│ │ ├── columns_helper.rb
│ │ ├── comments_helper.rb
│ │ ├── emoji_helper.rb
│ │ ├── entropy_helper.rb
│ │ ├── events_helper.rb
│ │ ├── excerpt_helper.rb
│ │ ├── filters_helper.rb
│ │ ├── forms_helper.rb
│ │ ├── hotkeys_helper.rb
│ │ ├── html_helper.rb
│ │ ├── login_helper.rb
│ │ ├── messages_helper.rb
│ │ ├── my/
│ │ │ └── menu_helper.rb
│ │ ├── notifications_helper.rb
│ │ ├── pagination_helper.rb
│ │ ├── qr_codes_helper.rb
│ │ ├── reactions_helper.rb
│ │ ├── rich_text_helper.rb
│ │ ├── tenanting_helper.rb
│ │ ├── time_helper.rb
│ │ ├── users_helper.rb
│ │ └── webhooks_helper.rb
│ ├── javascript/
│ │ ├── application.js
│ │ ├── controllers/
│ │ │ ├── application.js
│ │ │ ├── assignment_limit_controller.js
│ │ │ ├── auto_click_controller.js
│ │ │ ├── auto_save_controller.js
│ │ │ ├── auto_submit_controller.js
│ │ │ ├── autoresize_controller.js
│ │ │ ├── badge_controller.js
│ │ │ ├── bar_controller.js
│ │ │ ├── beacon_controller.js
│ │ │ ├── boards_form_controller.js
│ │ │ ├── bridge/
│ │ │ │ ├── buttons_controller.js
│ │ │ │ ├── form_controller.js
│ │ │ │ ├── insets_controller.js
│ │ │ │ ├── overflow_menu_controller.js
│ │ │ │ ├── share_controller.js
│ │ │ │ ├── stamp_controller.js
│ │ │ │ ├── text_size_controller.js
│ │ │ │ └── title_controller.js
│ │ │ ├── bubble_controller.js
│ │ │ ├── card_hotkeys_controller.js
│ │ │ ├── clear_offline_cache_controller.js
│ │ │ ├── clicker_controller.js
│ │ │ ├── collapsible_columns_controller.js
│ │ │ ├── combobox_controller.js
│ │ │ ├── copy_to_clipboard_controller.js
│ │ │ ├── css_variable_counter_controller.js
│ │ │ ├── details_controller.js
│ │ │ ├── dialog_controller.js
│ │ │ ├── dialog_manager_controller.js
│ │ │ ├── drag_and_drop_controller.js
│ │ │ ├── drag_and_strum_controller.js
│ │ │ ├── element_removal_controller.js
│ │ │ ├── expandable_on_native_controller.js
│ │ │ ├── fetch_on_visible_controller.js
│ │ │ ├── filter_controller.js
│ │ │ ├── filter_form_controller.js
│ │ │ ├── filter_settings_controller.js
│ │ │ ├── form_controller.js
│ │ │ ├── frame_controller.js
│ │ │ ├── frame_reloader_controller.js
│ │ │ ├── hotkey_controller.js
│ │ │ ├── index.js
│ │ │ ├── knob_controller.js
│ │ │ ├── lightbox_controller.js
│ │ │ ├── local_save_controller.js
│ │ │ ├── local_time_controller.js
│ │ │ ├── magic_link_controller.js
│ │ │ ├── multi_selection_combobox_controller.js
│ │ │ ├── nav_section_expander_controller.js
│ │ │ ├── navigable_list_controller.js
│ │ │ ├── notifications_controller.js
│ │ │ ├── outlet_auto_save_controller.js
│ │ │ ├── pagination_controller.js
│ │ │ ├── reaction_delete_controller.js
│ │ │ ├── reaction_emoji_controller.js
│ │ │ ├── related_element_controller.js
│ │ │ ├── retarget_links_controller.js
│ │ │ ├── scroll_to_controller.js
│ │ │ ├── search_form_controller.js
│ │ │ ├── soft_keyboard_controller.js
│ │ │ ├── syntax_highlight_controller.js
│ │ │ ├── theme_controller.js
│ │ │ ├── timezone_cookie_controller.js
│ │ │ ├── toggle_class_controller.js
│ │ │ ├── toggle_enable_controller.js
│ │ │ ├── tooltip_controller.js
│ │ │ ├── touch_placeholder_controller.js
│ │ │ ├── turbo_navigation_controller.js
│ │ │ └── upload_preview_controller.js
│ │ ├── helpers/
│ │ │ ├── bridge/
│ │ │ │ └── viewport_helpers.js
│ │ │ ├── date_helpers.js
│ │ │ ├── form_helpers.js
│ │ │ ├── html_helpers.js
│ │ │ ├── orientation_helpers.js
│ │ │ ├── platform_helpers.js
│ │ │ ├── scroll_helpers.js
│ │ │ ├── text_helpers.js
│ │ │ └── timing_helpers.js
│ │ ├── initializers/
│ │ │ ├── bridge/
│ │ │ │ └── bridge_element.js
│ │ │ ├── current.js
│ │ │ ├── index.js
│ │ │ ├── lexxy_markdown_paste.js
│ │ │ └── offline.js
│ │ └── lib/
│ │ └── action_pack/
│ │ ├── passkey.js
│ │ └── webauthn.js
│ ├── jobs/
│ │ ├── account/
│ │ │ ├── data_import_job.rb
│ │ │ └── incinerate_due_job.rb
│ │ ├── application_job.rb
│ │ ├── board/
│ │ │ └── clean_inaccessible_data_job.rb
│ │ ├── card/
│ │ │ ├── activity_spike/
│ │ │ │ └── detection_job.rb
│ │ │ ├── clean_inaccessible_data_job.rb
│ │ │ └── remove_inaccessible_notifications_job.rb
│ │ ├── concerns/
│ │ │ └── smtp_delivery_error_handling.rb
│ │ ├── data_export_job.rb
│ │ ├── delete_unused_tags_job.rb
│ │ ├── event/
│ │ │ └── webhook_dispatch_job.rb
│ │ ├── mention/
│ │ │ └── create_job.rb
│ │ ├── notification/
│ │ │ ├── bundle/
│ │ │ │ ├── deliver_all_job.rb
│ │ │ │ └── deliver_job.rb
│ │ │ └── push_job.rb
│ │ ├── notify_recipients_job.rb
│ │ ├── push_notification_job.rb
│ │ ├── storage/
│ │ │ ├── materialize_job.rb
│ │ │ └── reconcile_job.rb
│ │ └── webhook/
│ │ └── delivery_job.rb
│ ├── mailers/
│ │ ├── account_mailer.rb
│ │ ├── application_mailer.rb
│ │ ├── concerns/
│ │ │ └── mailers/
│ │ │ └── unsubscribable.rb
│ │ ├── export_mailer.rb
│ │ ├── import_mailer.rb
│ │ ├── magic_link_mailer.rb
│ │ ├── notification/
│ │ │ └── bundle_mailer.rb
│ │ └── user_mailer.rb
│ ├── models/
│ │ ├── access.rb
│ │ ├── account/
│ │ │ ├── cancellable.rb
│ │ │ ├── cancellation.rb
│ │ │ ├── data_transfer/
│ │ │ │ ├── account_record_set.rb
│ │ │ │ ├── action_text/
│ │ │ │ │ └── rich_text_record_set.rb
│ │ │ │ ├── active_storage/
│ │ │ │ │ ├── attachment_record_set.rb
│ │ │ │ │ ├── blob_record_set.rb
│ │ │ │ │ └── file_record_set.rb
│ │ │ │ ├── entropy_record_set.rb
│ │ │ │ ├── manifest.rb
│ │ │ │ ├── record_set.rb
│ │ │ │ └── user_record_set.rb
│ │ │ ├── entropic.rb
│ │ │ ├── export.rb
│ │ │ ├── external_id_sequence.rb
│ │ │ ├── import.rb
│ │ │ ├── incineratable.rb
│ │ │ ├── join_code.rb
│ │ │ ├── multi_tenantable.rb
│ │ │ ├── seedeable.rb
│ │ │ ├── seeder.rb
│ │ │ └── storage.rb
│ │ ├── account.rb
│ │ ├── admin.rb
│ │ ├── application_platform.rb
│ │ ├── application_record.rb
│ │ ├── assignment.rb
│ │ ├── board/
│ │ │ ├── accessible.rb
│ │ │ ├── auto_postponing.rb
│ │ │ ├── broadcastable.rb
│ │ │ ├── cards.rb
│ │ │ ├── entropic.rb
│ │ │ ├── publication.rb
│ │ │ ├── publishable.rb
│ │ │ ├── storage.rb
│ │ │ └── triageable.rb
│ │ ├── board.rb
│ │ ├── card/
│ │ │ ├── accessible.rb
│ │ │ ├── activity_spike/
│ │ │ │ └── detector.rb
│ │ │ ├── activity_spike.rb
│ │ │ ├── assignable.rb
│ │ │ ├── broadcastable.rb
│ │ │ ├── closeable.rb
│ │ │ ├── colored.rb
│ │ │ ├── commentable.rb
│ │ │ ├── entropic.rb
│ │ │ ├── entropy.rb
│ │ │ ├── eventable/
│ │ │ │ └── system_commenter.rb
│ │ │ ├── eventable.rb
│ │ │ ├── exportable.rb
│ │ │ ├── golden.rb
│ │ │ ├── goldness.rb
│ │ │ ├── mentions.rb
│ │ │ ├── multistep.rb
│ │ │ ├── not_now.rb
│ │ │ ├── pinnable.rb
│ │ │ ├── postponable.rb
│ │ │ ├── promptable.rb
│ │ │ ├── readable.rb
│ │ │ ├── searchable.rb
│ │ │ ├── stallable.rb
│ │ │ ├── statuses.rb
│ │ │ ├── taggable.rb
│ │ │ ├── triageable.rb
│ │ │ └── watchable.rb
│ │ ├── card.rb
│ │ ├── closure.rb
│ │ ├── color.rb
│ │ ├── column/
│ │ │ ├── colored.rb
│ │ │ └── positioned.rb
│ │ ├── column.rb
│ │ ├── comment/
│ │ │ ├── eventable.rb
│ │ │ ├── mentions.rb
│ │ │ ├── promptable.rb
│ │ │ └── searchable.rb
│ │ ├── comment.rb
│ │ ├── concerns/
│ │ │ ├── attachments.rb
│ │ │ ├── eventable.rb
│ │ │ ├── filterable.rb
│ │ │ ├── mentions.rb
│ │ │ ├── notifiable.rb
│ │ │ ├── searchable.rb
│ │ │ └── storage/
│ │ │ ├── totaled.rb
│ │ │ └── tracked.rb
│ │ ├── current.rb
│ │ ├── entropy.rb
│ │ ├── event/
│ │ │ ├── description.rb
│ │ │ ├── particulars.rb
│ │ │ └── promptable.rb
│ │ ├── event.rb
│ │ ├── export.rb
│ │ ├── filter/
│ │ │ ├── fields.rb
│ │ │ ├── params.rb
│ │ │ ├── resources.rb
│ │ │ └── summarized.rb
│ │ ├── filter.rb
│ │ ├── identity/
│ │ │ ├── access_token.rb
│ │ │ ├── joinable.rb
│ │ │ └── transferable.rb
│ │ ├── identity.rb
│ │ ├── magic_link/
│ │ │ └── code.rb
│ │ ├── magic_link.rb
│ │ ├── mention.rb
│ │ ├── notification/
│ │ │ ├── bundle.rb
│ │ │ ├── default_payload.rb
│ │ │ ├── event_payload.rb
│ │ │ ├── mention_payload.rb
│ │ │ ├── push_target/
│ │ │ │ └── web.rb
│ │ │ ├── push_target.rb
│ │ │ └── pushable.rb
│ │ ├── notification.rb
│ │ ├── notifier/
│ │ │ ├── card_event_notifier.rb
│ │ │ ├── comment_event_notifier.rb
│ │ │ └── mention_notifier.rb
│ │ ├── notifier.rb
│ │ ├── passkey/
│ │ │ └── authenticator.rb
│ │ ├── pin.rb
│ │ ├── push/
│ │ │ └── subscription.rb
│ │ ├── push.rb
│ │ ├── qr_code_link.rb
│ │ ├── reaction.rb
│ │ ├── search/
│ │ │ ├── highlighter.rb
│ │ │ ├── query.rb
│ │ │ ├── record/
│ │ │ │ ├── sqlite/
│ │ │ │ │ └── fts.rb
│ │ │ │ ├── sqlite.rb
│ │ │ │ └── trilogy.rb
│ │ │ ├── record.rb
│ │ │ ├── result.rb
│ │ │ └── stemmer.rb
│ │ ├── search.rb
│ │ ├── session.rb
│ │ ├── signup/
│ │ │ └── account_name_generator.rb
│ │ ├── signup.rb
│ │ ├── ssrf_protection.rb
│ │ ├── step.rb
│ │ ├── storage/
│ │ │ ├── attachment_tracking.rb
│ │ │ ├── entry.rb
│ │ │ └── total.rb
│ │ ├── storage.rb
│ │ ├── tag/
│ │ │ └── attachable.rb
│ │ ├── tag.rb
│ │ ├── tagging.rb
│ │ ├── time_window_parser.rb
│ │ ├── user/
│ │ │ ├── accessor.rb
│ │ │ ├── assignee.rb
│ │ │ ├── attachable.rb
│ │ │ ├── avatar.rb
│ │ │ ├── configurable.rb
│ │ │ ├── data_export.rb
│ │ │ ├── day_timeline/
│ │ │ │ ├── column.rb
│ │ │ │ └── serializable.rb
│ │ │ ├── day_timeline.rb
│ │ │ ├── email_address_changeable.rb
│ │ │ ├── filtering.rb
│ │ │ ├── mentionable.rb
│ │ │ ├── named.rb
│ │ │ ├── notifiable.rb
│ │ │ ├── role.rb
│ │ │ ├── searcher.rb
│ │ │ ├── settings.rb
│ │ │ ├── timelined.rb
│ │ │ ├── transferable.rb
│ │ │ └── watcher.rb
│ │ ├── user.rb
│ │ ├── watch.rb
│ │ ├── webhook/
│ │ │ ├── delinquency_tracker.rb
│ │ │ ├── delivery.rb
│ │ │ └── triggerable.rb
│ │ ├── webhook.rb
│ │ ├── zip_file/
│ │ │ ├── reader/
│ │ │ │ └── io.rb
│ │ │ ├── reader.rb
│ │ │ ├── remote_io.rb
│ │ │ └── writer.rb
│ │ └── zip_file.rb
│ └── views/
│ ├── account/
│ │ ├── exports/
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ ├── imports/
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ │ ├── join_codes/
│ │ │ ├── edit.html.erb
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ └── settings/
│ │ ├── _cancellation.html.erb
│ │ ├── _entropy.html.erb
│ │ ├── _export.html.erb
│ │ ├── _name.html.erb
│ │ ├── _user.html.erb
│ │ ├── _users.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jbuilder
│ ├── action_text/
│ │ └── attachables/
│ │ ├── _remote_image.html.erb
│ │ └── _remote_video.html.erb
│ ├── active_storage/
│ │ └── blobs/
│ │ ├── _blob.html.erb
│ │ └── web/
│ │ └── _representation.html.erb
│ ├── bar/
│ │ └── _bar.html.erb
│ ├── boards/
│ │ ├── _access_toggle.html.erb
│ │ ├── _board.json.jbuilder
│ │ ├── columns/
│ │ │ ├── _empty_placeholder.html.erb
│ │ │ ├── closeds/
│ │ │ │ ├── show.html.erb
│ │ │ │ └── show.json.jbuilder
│ │ │ ├── create.turbo_stream.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── not_nows/
│ │ │ │ ├── show.html.erb
│ │ │ │ └── show.json.jbuilder
│ │ │ ├── show.html.erb
│ │ │ ├── show.json.jbuilder
│ │ │ ├── streams/
│ │ │ │ ├── show.html.erb
│ │ │ │ └── show.json.jbuilder
│ │ │ └── update.turbo_stream.erb
│ │ ├── edit/
│ │ │ ├── _auto_close.html.erb
│ │ │ ├── _delete.html.erb
│ │ │ ├── _name.html.erb
│ │ │ ├── _publication.html.erb
│ │ │ └── _users.html.erb
│ │ ├── edit.html.erb
│ │ ├── entropies/
│ │ │ └── update.turbo_stream.erb
│ │ ├── index.json.jbuilder
│ │ ├── involvements/
│ │ │ └── update.html.erb
│ │ ├── new.html.erb
│ │ ├── publications/
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── destroy.turbo_stream.erb
│ │ ├── show/
│ │ │ ├── _closed.html.erb
│ │ │ ├── _column.html.erb
│ │ │ ├── _columns.html.erb
│ │ │ ├── _expander.html.erb
│ │ │ ├── _filtered_cards.html.erb
│ │ │ ├── _not_now.html.erb
│ │ │ ├── _stream.html.erb
│ │ │ └── menu/
│ │ │ ├── _column.html.erb
│ │ │ ├── _column_form.html.erb
│ │ │ ├── _columns.html.erb
│ │ │ └── _maximize.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jbuilder
│ ├── cards/
│ │ ├── _broadcasts.html.erb
│ │ ├── _card.json.jbuilder
│ │ ├── _container.html.erb
│ │ ├── _delete.html.erb
│ │ ├── _messages.html.erb
│ │ ├── assignments/
│ │ │ ├── _user.html.erb
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── new.html.erb
│ │ ├── boards/
│ │ │ └── edit.html.erb
│ │ ├── closures/
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── destroy.turbo_stream.erb
│ │ ├── columns/
│ │ │ ├── _column.html.erb
│ │ │ └── edit.html.erb
│ │ ├── comments/
│ │ │ ├── _comment.html.erb
│ │ │ ├── _comment.json.jbuilder
│ │ │ ├── _new.html.erb
│ │ │ ├── _watchers.html.erb
│ │ │ ├── create.turbo_stream.erb
│ │ │ ├── destroy.turbo_stream.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── show.html.erb
│ │ │ ├── show.json.jbuilder
│ │ │ └── update.turbo_stream.erb
│ │ ├── container/
│ │ │ ├── _closure.html.erb
│ │ │ ├── _closure_buttons.html.erb
│ │ │ ├── _content.html.erb
│ │ │ ├── _content_display.html.erb
│ │ │ ├── _gild.html.erb
│ │ │ ├── _image.html.erb
│ │ │ ├── _save_button.html.erb
│ │ │ └── footer/
│ │ │ ├── _create.html.erb
│ │ │ └── _published.html.erb
│ │ ├── display/
│ │ │ ├── _preview.html.erb
│ │ │ ├── _previews.html.erb
│ │ │ ├── _public_preview.html.erb
│ │ │ ├── _public_previews.html.erb
│ │ │ ├── common/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _background.html.erb
│ │ │ │ ├── _board.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ └── _stamp.html.erb
│ │ │ ├── mini/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ └── _tags.html.erb
│ │ │ ├── perma/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _background.html.erb
│ │ │ │ ├── _board.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ ├── _steps.html.erb
│ │ │ │ └── _tags.html.erb
│ │ │ ├── preview/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _board.html.erb
│ │ │ │ ├── _boosts.html.erb
│ │ │ │ ├── _bubble.html.erb
│ │ │ │ ├── _columns.html.erb
│ │ │ │ ├── _comments.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ ├── _people.html.erb
│ │ │ │ ├── _steps.html.erb
│ │ │ │ └── _tags.html.erb
│ │ │ └── public_preview/
│ │ │ ├── _columns.html.erb
│ │ │ └── _meta.html.erb
│ │ ├── drafts/
│ │ │ ├── _container.html.erb
│ │ │ └── show.html.erb
│ │ ├── edit.html.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── not_nows/
│ │ │ └── create.turbo_stream.erb
│ │ ├── pins/
│ │ │ ├── _pin_button.html.erb
│ │ │ └── show.html.erb
│ │ ├── previews/
│ │ │ └── index.turbo_stream.erb
│ │ ├── readings/
│ │ │ └── create.turbo_stream.erb
│ │ ├── show.html.erb
│ │ ├── show.json.jbuilder
│ │ ├── steps/
│ │ │ ├── _step.html.erb
│ │ │ ├── _step.json.jbuilder
│ │ │ ├── create.turbo_stream.erb
│ │ │ ├── destroy.turbo_stream.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── show.html.erb
│ │ │ ├── show.json.jbuilder
│ │ │ └── update.turbo_stream.erb
│ │ ├── taggings/
│ │ │ ├── _tag.html.erb
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── new.html.erb
│ │ ├── triage/
│ │ │ └── _columns.html.erb
│ │ ├── update.turbo_stream.erb
│ │ └── watches/
│ │ ├── _refresh.turbo_stream.erb
│ │ ├── _watch_button.html.erb
│ │ ├── create.turbo_stream.erb
│ │ ├── destroy.turbo_stream.erb
│ │ └── show.html.erb
│ ├── client_configurations/
│ │ ├── android_v1.json
│ │ └── ios_v1.json
│ ├── columns/
│ │ ├── _column.json.jbuilder
│ │ ├── _refresh_adjacent_columns.turbo_stream.erb
│ │ ├── cards/
│ │ │ └── drops/
│ │ │ ├── closures/
│ │ │ │ └── create.turbo_stream.erb
│ │ │ ├── columns/
│ │ │ │ └── create.turbo_stream.erb
│ │ │ ├── not_nows/
│ │ │ │ └── create.turbo_stream.erb
│ │ │ └── streams/
│ │ │ └── create.turbo_stream.erb
│ │ ├── left_positions/
│ │ │ └── create.turbo_stream.erb
│ │ ├── right_positions/
│ │ │ └── create.turbo_stream.erb
│ │ └── show/
│ │ └── _add_card_button.html.erb
│ ├── entropy/
│ │ ├── _auto_close.html.erb
│ │ └── _knob.html.erb
│ ├── event_summaries/
│ │ └── _event_summary.html.erb
│ ├── events/
│ │ ├── _day.html.erb
│ │ ├── _empty_days.html.erb
│ │ ├── _event.html.erb
│ │ ├── day_timeline/
│ │ │ ├── _column.html.erb
│ │ │ ├── _columns.html.erb
│ │ │ └── columns/
│ │ │ ├── _events.html.erb
│ │ │ └── show.html.erb
│ │ ├── days/
│ │ │ └── index.html.erb
│ │ ├── event/
│ │ │ ├── _attachments.html.erb
│ │ │ ├── _layout.html.erb
│ │ │ ├── attachments/
│ │ │ │ ├── _attachment.html.erb
│ │ │ │ ├── _remote_image.html.erb
│ │ │ │ └── _remote_video.html.erb
│ │ │ └── eventable/
│ │ │ ├── _card.html.erb
│ │ │ ├── _card_published.html.erb
│ │ │ └── _comment.html.erb
│ │ ├── index/
│ │ │ ├── _add_board_button.html.erb
│ │ │ ├── _add_card_button.html.erb
│ │ │ ├── _filter.html.erb
│ │ │ └── filter/
│ │ │ ├── _board.html.erb
│ │ │ └── _user.html.erb
│ │ └── index.html.erb
│ ├── filters/
│ │ ├── _filter_toggle.html.erb
│ │ ├── _settings.html.erb
│ │ ├── create.turbo_stream.erb
│ │ ├── destroy.turbo_stream.erb
│ │ ├── settings/
│ │ │ ├── _assignees.html.erb
│ │ │ ├── _boards.html.erb
│ │ │ ├── _cards.html.erb
│ │ │ ├── _closers.html.erb
│ │ │ ├── _controls.html.erb
│ │ │ ├── _creators.html.erb
│ │ │ ├── _indexed_by.html.erb
│ │ │ ├── _manage.html.erb
│ │ │ ├── _sorted_by.html.erb
│ │ │ ├── _tags.html.erb
│ │ │ ├── _terms.html.erb
│ │ │ ├── _time_window.html.erb
│ │ │ └── _toggle.html.erb
│ │ └── settings_refreshes/
│ │ └── create.turbo_stream.erb
│ ├── join_codes/
│ │ ├── inactive.html.erb
│ │ └── new.html.erb
│ ├── layouts/
│ │ ├── _lightbox.html.erb
│ │ ├── _theme_preference.html.erb
│ │ ├── action_text/
│ │ │ └── contents/
│ │ │ └── _content.html.erb
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ ├── mailer.text.erb
│ │ ├── public.html.erb
│ │ └── shared/
│ │ ├── _colophon.html.erb
│ │ ├── _flash.html.erb
│ │ ├── _head.html.erb
│ │ ├── _time_zone.html.erb
│ │ ├── _user_css.html.erb
│ │ └── _welcome_letter.html.erb
│ ├── mailers/
│ │ ├── account_mailer/
│ │ │ ├── cancellation.html.erb
│ │ │ └── cancellation.text.erb
│ │ ├── export_mailer/
│ │ │ ├── completed.html.erb
│ │ │ └── completed.text.erb
│ │ ├── identity_mailer/
│ │ │ └── email_change_confirmation.text.erb
│ │ ├── import_mailer/
│ │ │ ├── completed.html.erb
│ │ │ ├── completed.text.erb
│ │ │ ├── failed.html.erb
│ │ │ └── failed.text.erb
│ │ ├── magic_link_mailer/
│ │ │ ├── sign_in_instructions.html.erb
│ │ │ └── sign_in_instructions.text.erb
│ │ └── notification/
│ │ └── bundle_mailer/
│ │ ├── _notification.html.erb
│ │ ├── _notification.text.erb
│ │ ├── event/
│ │ │ ├── _body.html.erb
│ │ │ └── _body.text.erb
│ │ ├── mention/
│ │ │ ├── _body.html.erb
│ │ │ └── _body.text.erb
│ │ ├── notification.html.erb
│ │ └── notification.text.erb
│ ├── my/
│ │ ├── _menu.html.erb
│ │ ├── access_tokens/
│ │ │ ├── _access_token.html.erb
│ │ │ ├── _access_token.json.jbuilder
│ │ │ ├── index.html.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ │ ├── identities/
│ │ │ ├── _account.json.jbuilder
│ │ │ └── show.json.jbuilder
│ │ ├── menus/
│ │ │ ├── _accounts.html.erb
│ │ │ ├── _boards.html.erb
│ │ │ ├── _custom_views.html.erb
│ │ │ ├── _jump.html.erb
│ │ │ ├── _people.html.erb
│ │ │ ├── _settings.html.erb
│ │ │ ├── _shortcuts.html.erb
│ │ │ ├── _tags.html.erb
│ │ │ └── show.html.erb
│ │ ├── passkeys/
│ │ │ ├── _passkey.html.erb
│ │ │ ├── edit.html.erb
│ │ │ └── index.html.erb
│ │ └── pins/
│ │ ├── _pin.html.erb
│ │ ├── _tray.html.erb
│ │ ├── index.html.erb
│ │ └── index.json.jbuilder
│ ├── notifications/
│ │ ├── _notification.html.erb
│ │ ├── _notification.json.jbuilder
│ │ ├── _tray.html.erb
│ │ ├── index/
│ │ │ ├── _read_notifications.html.erb
│ │ │ └── _unread_notifications.html.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── index.turbo_stream.erb
│ │ ├── notification/
│ │ │ ├── _body.html.erb
│ │ │ ├── _header.html.erb
│ │ │ ├── event/
│ │ │ │ ├── _body.html.erb
│ │ │ │ └── _body.json.jbuilder
│ │ │ └── mention/
│ │ │ ├── _body.html.erb
│ │ │ └── _body.json.jbuilder
│ │ ├── readings/
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── destroy.turbo_stream.erb
│ │ ├── settings/
│ │ │ ├── _board.html.erb
│ │ │ ├── _browser.html.erb
│ │ │ ├── _email.html.erb
│ │ │ ├── _install.html.erb
│ │ │ ├── _push_notifications.html.erb
│ │ │ ├── _system.html.erb
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ ├── trays/
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ └── unsubscribes/
│ │ ├── new.html.erb
│ │ └── show.html.erb
│ ├── prompts/
│ │ ├── boards/
│ │ │ └── users/
│ │ │ ├── _user.html.erb
│ │ │ └── index.html.erb
│ │ ├── cards/
│ │ │ ├── _card.html.erb
│ │ │ └── index.html.erb
│ │ ├── commands/
│ │ │ ├── _command.html.erb
│ │ │ └── index.html.erb
│ │ ├── tags/
│ │ │ ├── _tag.html.erb
│ │ │ └── index.html.erb
│ │ └── users/
│ │ └── index.html.erb
│ ├── public/
│ │ ├── _footer.html.erb
│ │ ├── boards/
│ │ │ ├── card_previews/
│ │ │ │ └── index.turbo_stream.erb
│ │ │ ├── columns/
│ │ │ │ ├── closeds/
│ │ │ │ │ └── show.html.erb
│ │ │ │ ├── not_nows/
│ │ │ │ │ └── show.html.erb
│ │ │ │ ├── show.html.erb
│ │ │ │ └── streams/
│ │ │ │ └── show.html.erb
│ │ │ ├── show/
│ │ │ │ ├── _closed.html.erb
│ │ │ │ ├── _column.html.erb
│ │ │ │ ├── _columns.html.erb
│ │ │ │ ├── _not_now.html.erb
│ │ │ │ └── _stream.html.erb
│ │ │ └── show.html.erb
│ │ └── cards/
│ │ ├── show/
│ │ │ ├── _content.html.erb
│ │ │ └── _steps.html.erb
│ │ └── show.html.erb
│ ├── pwa/
│ │ ├── manifest.json.erb
│ │ └── service_worker.js.erb
│ ├── reactions/
│ │ ├── _menu.html.erb
│ │ ├── _reaction.html.erb
│ │ ├── _reaction.json.jbuilder
│ │ ├── _reactions.html.erb
│ │ ├── create.turbo_stream.erb
│ │ ├── destroy.turbo_stream.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── new.html.erb
│ │ └── show.json.jbuilder
│ ├── searches/
│ │ ├── _form.html.erb
│ │ ├── _result.html.erb
│ │ ├── _results.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jbuilder
│ ├── sessions/
│ │ ├── _footer.html.erb
│ │ ├── magic_links/
│ │ │ └── show.html.erb
│ │ ├── menus/
│ │ │ └── show.html.erb
│ │ ├── new.html.erb
│ │ ├── starts/
│ │ │ └── new.html.erb
│ │ └── transfers/
│ │ └── show.html.erb
│ ├── signups/
│ │ ├── completions/
│ │ │ └── new.html.erb
│ │ └── new.html.erb
│ ├── tags/
│ │ ├── _tag.json.jbuilder
│ │ ├── index.html.erb
│ │ └── index.json.jbuilder
│ ├── user_mailer/
│ │ └── email_change_confirmation.html.erb
│ ├── users/
│ │ ├── _access_tokens.html.erb
│ │ ├── _activity_timeline.html.erb
│ │ ├── _attachable.html.erb
│ │ ├── _data_export.html.erb
│ │ ├── _theme.html.erb
│ │ ├── _transfer.html.erb
│ │ ├── _user.json.jbuilder
│ │ ├── avatars/
│ │ │ └── show.svg.erb
│ │ ├── data_exports/
│ │ │ └── show.html.erb
│ │ ├── edit.html.erb
│ │ ├── email_addresses/
│ │ │ ├── confirmations/
│ │ │ │ ├── invalid_token.html.erb
│ │ │ │ └── show.html.erb
│ │ │ ├── create.html.erb
│ │ │ └── new.html.erb
│ │ ├── events/
│ │ │ └── show.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── joins/
│ │ │ └── new.html.erb
│ │ ├── show.html.erb
│ │ ├── show.json.jbuilder
│ │ └── verifications/
│ │ └── new.html.erb
│ └── webhooks/
│ ├── _delivery.html.erb
│ ├── _webhook.html.erb
│ ├── _webhook.json.jbuilder
│ ├── edit.html.erb
│ ├── event.html.erb
│ ├── event.json.jbuilder
│ ├── form/
│ │ └── _actions.html.erb
│ ├── index.html.erb
│ ├── index.json.jbuilder
│ ├── new.html.erb
│ ├── show.html.erb
│ └── show.json.jbuilder
├── bin/
│ ├── brakeman
│ ├── bundle-both
│ ├── bundle-drift
│ ├── bundler-audit
│ ├── ci
│ ├── dev
│ ├── docker-entrypoint
│ ├── gitleaks-audit
│ ├── importmap
│ ├── jobs
│ ├── kamal
│ ├── minio-setup
│ ├── notify_dash_of_deployment
│ ├── rails
│ ├── rake
│ ├── rubocop
│ ├── setup
│ └── thrust
├── config/
│ ├── application.rb
│ ├── boot.rb
│ ├── brakeman.ignore
│ ├── cable.yml
│ ├── cache.yml
│ ├── ci.rb
│ ├── database.mysql.yml
│ ├── database.sqlite.yml
│ ├── database.yml
│ ├── deploy.yml
│ ├── environment.rb
│ ├── environments/
│ │ ├── beta.rb
│ │ ├── development.rb
│ │ ├── production.rb
│ │ ├── staging.rb
│ │ └── test.rb
│ ├── importmap.rb
│ ├── initializers/
│ │ ├── action_text.rb
│ │ ├── active_job.rb
│ │ ├── active_storage.rb
│ │ ├── active_storage_no_reuse.rb
│ │ ├── active_storage_purge_on_last_attachment.rb
│ │ ├── assets.rb
│ │ ├── autotuner.rb
│ │ ├── content_security_policy.rb
│ │ ├── database_role_logging.rb
│ │ ├── error_context.rb
│ │ ├── extensions.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mission_control.rb
│ │ ├── multi_db.rb
│ │ ├── multi_tenant.rb
│ │ ├── passkeys.rb
│ │ ├── permissions_policy.rb
│ │ ├── push_notifications.rb
│ │ ├── rack_mini_profiler.rb
│ │ ├── sanitization.rb
│ │ ├── sqlite_schema_dumper.rb
│ │ ├── table_definition_column_limits.rb
│ │ ├── tenanting/
│ │ │ ├── account_slug.rb
│ │ │ └── turbo.rb
│ │ ├── uuid_framework_models.rb
│ │ ├── uuid_primary_keys.rb
│ │ ├── vapid.rb
│ │ ├── vips.rb
│ │ └── web_push.rb
│ ├── locales/
│ │ └── en.yml
│ ├── passkey_aaguids.yml
│ ├── puma.rb
│ ├── queue.yml
│ ├── recurring.yml
│ ├── routes.rb
│ ├── storage.oss.yml
│ └── storage.yml
├── config.ru
├── db/
│ ├── cable_schema.rb
│ ├── cache_schema.rb
│ ├── migrate/
│ │ ├── 20251111122540_initial_schema.rb
│ │ ├── 20251111153019_add_number_to_cards.rb
│ │ ├── 20251112093037_create_search_indices.rb
│ │ ├── 20251112184932_remove_join_code_from_memberships.rb
│ │ ├── 20251113111501_drop_memberships.rb
│ │ ├── 20251113160907_add_missing_account_id_columns.rb
│ │ ├── 20251113163145_ensure_account_id_index.rb
│ │ ├── 20251113190256_create_search_record_shards.rb
│ │ ├── 20251114084325_drop_search_results.rb
│ │ ├── 20251114183203_ensure_an_identit_can_only_have_one_user_in_an_account.rb
│ │ ├── 20251117190817_change_endpoint_to_text_in_push_subscriptions.rb
│ │ ├── 20251117192434_change_external_account_id_to_bigint_in_accounts.rb
│ │ ├── 20251117202517_change_usage_limit_to_bigint_in_account_join_codes.rb
│ │ ├── 20251120110206_add_search_records.rb
│ │ ├── 20251120194700_remove_all_foreign_key_constraints.rb
│ │ ├── 20251120203100_add_unique_index_to_card_activity_spikes_on_card_id.rb
│ │ ├── 20251121092508_add_account_key_to_search_records.rb
│ │ ├── 20251121112416_remove_old_fulltext_indexes_from_search_records.rb
│ │ ├── 20251125110629_increase_user_agent_length.rb
│ │ ├── 20251125130010_add_a_staff_flag_to_identities.rb
│ │ ├── 20251127000001_create_account_external_id_sequences.rb
│ │ ├── 20251129110120_add_purpose_to_magic_links.rb
│ │ ├── 20251129175717_promote_first_admin_to_owner.rb
│ │ ├── 20251201100607_create_account_exports.rb
│ │ ├── 20251201132341_create_identity_access_tokens.rb
│ │ ├── 20251205010536_add_verified_at_to_users.rb
│ │ ├── 20251205205826_create_storage_tables.rb
│ │ ├── 20251210054934_add_blob_id_and_audit_context_to_storage_entries.rb
│ │ ├── 20251219120755_drop_card_engagements.rb
│ │ ├── 20251223000001_rename_account_exports_to_exports.rb
│ │ ├── 20251223000002_create_account_imports.rb
│ │ ├── 20251224092315_create_account_cancellations.rb
│ │ ├── 20260121155752_make_reactions_polymorphic.rb
│ │ ├── 20260206104338_add_card_id_to_notifications.rb
│ │ ├── 20260209165805_notifications_data_migration.rb
│ │ ├── 20260211122517_add_failure_reason_to_account_imports.rb
│ │ ├── 20260212102026_fix_notifications_ordered_index.rb
│ │ ├── 20260213154740_create_action_pack_passkeys.rb
│ │ ├── 20260213170100_add_created_at_index_to_webhook_deliveries.rb
│ │ └── 20260218120000_restore_unique_index_on_board_publication_key.rb
│ ├── queue_schema.rb
│ ├── schema.rb
│ ├── schema_sqlite.rb
│ ├── seeds/
│ │ ├── 37signals.rb
│ │ ├── cleanslate.rb
│ │ └── honcho.rb
│ └── seeds.rb
├── docs/
│ ├── API.md
│ ├── development.md
│ ├── docker-deployment.md
│ └── kamal-deployment.md
├── lib/
│ ├── action_pack/
│ │ ├── passkey/
│ │ │ ├── challenges_controller.rb
│ │ │ ├── form_helper.rb
│ │ │ ├── holder.rb
│ │ │ └── request.rb
│ │ ├── passkey.rb
│ │ ├── railtie.rb
│ │ ├── web_authn/
│ │ │ ├── authenticator/
│ │ │ │ ├── assertion_response.rb
│ │ │ │ ├── attestation.rb
│ │ │ │ ├── attestation_response.rb
│ │ │ │ ├── attestation_verifiers/
│ │ │ │ │ └── none.rb
│ │ │ │ ├── data.rb
│ │ │ │ └── response.rb
│ │ │ ├── cbor_decoder.rb
│ │ │ ├── cose_key.rb
│ │ │ ├── current.rb
│ │ │ ├── public_key_credential/
│ │ │ │ ├── creation_options.rb
│ │ │ │ ├── options.rb
│ │ │ │ └── request_options.rb
│ │ │ ├── public_key_credential.rb
│ │ │ └── relying_party.rb
│ │ └── web_authn.rb
│ ├── assets/
│ │ └── .keep
│ ├── auto_link_scrubber.rb
│ ├── deployment/
│ │ └── database_resolver.rb
│ ├── deployment.rb
│ ├── fizzy.rb
│ ├── rails_ext/
│ │ ├── action_mailer_mail_delivery_job.rb
│ │ ├── action_pack_passkey_infer_name_from_aaguid.rb
│ │ ├── active_record_date_arithmetic.rb
│ │ ├── active_record_replica_support.rb
│ │ ├── active_record_uuid_type.rb
│ │ ├── active_storage_analyze_job_skip_detached.rb
│ │ ├── active_storage_analyze_job_suppress_broadcasts.rb
│ │ ├── active_storage_authorization.rb
│ │ ├── active_storage_blob_service_url_for_direct_upload_expiry.rb
│ │ ├── active_support_array_conversions.rb
│ │ ├── prepend_order.rb
│ │ └── string.rb
│ ├── tasks/
│ │ ├── dev.rake
│ │ ├── saas.rake
│ │ └── search.rake
│ └── web_push/
│ ├── notification.rb
│ └── pool.rb
├── log/
│ └── .keep
├── public/
│ ├── 400.html
│ ├── 404.html
│ ├── 406-unsupported-browser.html
│ ├── 422.html
│ ├── 500.html
│ ├── error.css
│ └── robots.txt
├── saas/
│ ├── .kamal/
│ │ ├── hooks/
│ │ │ ├── post-deploy
│ │ │ └── pre-connect
│ │ ├── secrets.beta
│ │ ├── secrets.production
│ │ └── secrets.staging
│ ├── Dockerfile
│ ├── LICENSE.md
│ ├── README.md
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ └── images/
│ │ │ └── fizzy/
│ │ │ └── saas/
│ │ │ └── .keep
│ │ ├── controllers/
│ │ │ ├── admin/
│ │ │ │ ├── audits_controller.rb
│ │ │ │ └── stats_controller.rb
│ │ │ ├── concerns/
│ │ │ │ └── card/
│ │ │ │ ├── storage_limited/
│ │ │ │ │ ├── commenting.rb
│ │ │ │ │ ├── creation.rb
│ │ │ │ │ └── publishing.rb
│ │ │ │ └── storage_limited.rb
│ │ │ └── my/
│ │ │ └── devices_controller.rb
│ │ ├── jobs/
│ │ │ └── application_push_notification_job.rb
│ │ ├── models/
│ │ │ ├── account/
│ │ │ │ ├── storage_exception.rb
│ │ │ │ └── storage_limited.rb
│ │ │ ├── application_push_device.rb
│ │ │ ├── application_push_notification.rb
│ │ │ ├── identity/
│ │ │ │ └── devices.rb
│ │ │ ├── notification/
│ │ │ │ └── push_target/
│ │ │ │ └── native.rb
│ │ │ ├── saas_record.rb
│ │ │ ├── session/
│ │ │ │ └── devices.rb
│ │ │ └── subscription.rb
│ │ └── views/
│ │ ├── admin/
│ │ │ └── stats/
│ │ │ └── show.html.erb
│ │ ├── cards/
│ │ │ ├── comments/
│ │ │ │ └── saas/
│ │ │ │ ├── _new.html.erb
│ │ │ │ └── _storage_limit_exceeded.html.erb
│ │ │ └── container/
│ │ │ └── footer/
│ │ │ └── saas/
│ │ │ ├── _create.html.erb
│ │ │ ├── _storage_limit_exceeded.html.erb
│ │ │ └── _storage_limit_notice.html.erb
│ │ ├── layouts/
│ │ │ └── fizzy/
│ │ │ └── saas/
│ │ │ └── application.html.erb
│ │ ├── my/
│ │ │ └── devices/
│ │ │ └── index.html.erb
│ │ ├── notifications/
│ │ │ └── settings/
│ │ │ └── _native_devices.html.erb
│ │ └── signup/
│ │ ├── completions/
│ │ │ └── new.html.erb
│ │ └── new.html.erb
│ ├── bin/
│ │ ├── broadcast_to_bc
│ │ └── setup
│ ├── config/
│ │ ├── database.yml
│ │ ├── deploy.beta.yml
│ │ ├── deploy.beta1.yml
│ │ ├── deploy.beta2.yml
│ │ ├── deploy.beta3.yml
│ │ ├── deploy.beta4.yml
│ │ ├── deploy.production.yml
│ │ ├── deploy.staging.yml
│ │ ├── deploy.yml
│ │ ├── environments/
│ │ │ ├── beta.rb
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── staging.rb
│ │ ├── push.yml
│ │ ├── routes.rb
│ │ └── storage.yml
│ ├── db/
│ │ ├── migrate/
│ │ │ ├── 20251202200249_create_console1984_tables.console1984.rb
│ │ │ ├── 20251202205753_create_auditing_tables.audits1984.rb
│ │ │ ├── 20251203144630_create_account_subscriptions.rb
│ │ │ ├── 20251215140000_create_account_overridden_limits.rb
│ │ │ ├── 20251215160000_create_account_billing_waivers.rb
│ │ │ ├── 20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb
│ │ │ ├── 20251216000000_add_bytes_used_to_account_overridden_limits.rb
│ │ │ ├── 20260114203313_create_action_push_native_devices.rb
│ │ │ ├── 20260126230838_create_auditor_tokens.audits1984.rb
│ │ │ ├── 20260317000000_drop_billing_tables.rb
│ │ │ └── 20260319142914_create_account_storage_exceptions.rb
│ │ └── saas_schema.rb
│ ├── exe/
│ │ └── push-dev
│ ├── fizzy-saas.gemspec
│ ├── lib/
│ │ ├── fizzy/
│ │ │ ├── saas/
│ │ │ │ ├── authorization.rb
│ │ │ │ ├── engine.rb
│ │ │ │ ├── gvl_instrumentation.rb
│ │ │ │ ├── metrics.rb
│ │ │ │ ├── signup.rb
│ │ │ │ ├── testing.rb
│ │ │ │ ├── transaction_pinning.rb
│ │ │ │ ├── true_client_ip.rb
│ │ │ │ └── version.rb
│ │ │ └── saas.rb
│ │ ├── rails_ext/
│ │ │ └── active_record_tasks_database_tasks.rb
│ │ ├── tasks/
│ │ │ └── fizzy/
│ │ │ └── saas_tasks.rake
│ │ └── yabeda/
│ │ ├── gvl.rb
│ │ └── solid_queue.rb
│ ├── public/
│ │ └── .well-known/
│ │ ├── apple-app-site-association
│ │ └── assetlinks.json
│ ├── script/
│ │ ├── configure-lb-beta.sh
│ │ ├── configure-lb-production.sh
│ │ └── configure-lb-staging.sh
│ └── test/
│ ├── controllers/
│ │ ├── .keep
│ │ ├── admin/
│ │ │ ├── audits_controller_test.rb
│ │ │ └── stats_controller_test.rb
│ │ ├── card/
│ │ │ ├── storage_limited/
│ │ │ │ ├── commenting_test.rb
│ │ │ │ ├── creation_test.rb
│ │ │ │ └── publishing_test.rb
│ │ │ └── storage_limited_test.rb
│ │ ├── comment/
│ │ │ └── storage_limited_test.rb
│ │ ├── my/
│ │ │ └── devices_controller_test.rb
│ │ └── non_production_remote_access_test.rb
│ ├── fixtures/
│ │ ├── application_push_devices.yml
│ │ └── files/
│ │ └── .keep
│ ├── helpers/
│ │ └── .keep
│ ├── integration/
│ │ └── .keep
│ ├── lib/
│ │ └── true_client_ip_test.rb
│ ├── mailers/
│ │ └── .keep
│ └── models/
│ ├── account/
│ │ ├── storage_exception_test.rb
│ │ └── storage_limited_test.rb
│ ├── identity_test.rb
│ ├── notification/
│ │ └── push_target/
│ │ └── native_test.rb
│ ├── session/
│ │ └── devices_test.rb
│ └── signup_test.rb
├── script/
│ ├── create-identities.rb
│ ├── fetch-prod-db.rb
│ ├── fix-active-storage-links.rb
│ ├── import-sqlite-database.rb
│ ├── load-prod-db-in-dev.rb
│ ├── maintenance/
│ │ ├── fix_cross_account_taggings.rb
│ │ ├── remove_duplicated_search_queries.rb
│ │ └── remove_duplicated_tags.rb
│ ├── migrations/
│ │ ├── 20250924-populate-identities.rb
│ │ ├── 20251028-populate_membership_id_on_users.rb
│ │ ├── 20251029-populate-column-positions.rb
│ │ ├── 20251205-backfill-verified-at.rb
│ │ ├── 20260123-remove-draft-cards-from-search-index.rb
│ │ ├── 20260204-fix-misplaced-comment-events.rb
│ │ ├── backfill-storage-ledger.rb
│ │ ├── convert-absolute-attachment-urls-to-relative.rb
│ │ ├── convert-relative-attachment-urls-to-absolute.rb
│ │ ├── copy-blobs-to-pure.rb
│ │ ├── fill_account_closure_reasons.rb
│ │ ├── generate_comments_from_events.rb
│ │ ├── migrate-content-to-slugged-urls.rb
│ │ ├── migrate-disk-service-blobs.rb
│ │ ├── migrate_to_flat_card_urls.rb
│ │ ├── migrate_to_new_cards_url_scheme.rb
│ │ ├── populate_columns_from_workflow_stages.rb
│ │ ├── renaming/
│ │ │ ├── content.rb
│ │ │ └── files.rb
│ │ ├── reset_boards_ids.rb
│ │ ├── reset_cards_ids.rb
│ │ └── split-sibling-paragraphs-with-p-br.rb
│ ├── populate.rb
│ └── remove-lb-admin-production.sh
├── storage/
│ └── .keep
├── test/
│ ├── application_system_test_case.rb
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection_test.rb
│ ├── controllers/
│ │ ├── account/
│ │ │ └── cancellations_controller_test.rb
│ │ ├── accounts/
│ │ │ ├── entropies_controller_test.rb
│ │ │ ├── exports_controller_test.rb
│ │ │ ├── join_codes_controller_test.rb
│ │ │ └── settings_controller_test.rb
│ │ ├── active_storage/
│ │ │ └── direct_uploads_controller_test.rb
│ │ ├── admin/
│ │ │ └── mission_control_test.rb
│ │ ├── allow_browser_test.rb
│ │ ├── api/
│ │ │ └── flat_json_params_test.rb
│ │ ├── api_test.rb
│ │ ├── boards/
│ │ │ ├── columns/
│ │ │ │ ├── closeds_controller_test.rb
│ │ │ │ ├── not_nows_controller_test.rb
│ │ │ │ └── streams_controller_test.rb
│ │ │ ├── columns_controller_test.rb
│ │ │ ├── entropies_controller_test.rb
│ │ │ ├── involvements_controller_test.rb
│ │ │ └── publications_controller_test.rb
│ │ ├── boards_controller_test.rb
│ │ ├── cards/
│ │ │ ├── assignments_controller_test.rb
│ │ │ ├── boards_controller_test.rb
│ │ │ ├── closures_controller_test.rb
│ │ │ ├── comments/
│ │ │ │ └── reactions_controller_test.rb
│ │ │ ├── comments_controller_test.rb
│ │ │ ├── drafts_controller_test.rb
│ │ │ ├── goldnesses_controller_test.rb
│ │ │ ├── images_controller_test.rb
│ │ │ ├── not_nows_controller_test.rb
│ │ │ ├── pins_controller_test.rb
│ │ │ ├── previews_controller_test.rb
│ │ │ ├── publishes_controller_test.rb
│ │ │ ├── reactions_controller_test.rb
│ │ │ ├── readings_controller_test.rb
│ │ │ ├── self_assignments_controller_test.rb
│ │ │ ├── steps_controller_test.rb
│ │ │ ├── taggings_controller_test.rb
│ │ │ ├── triages_controller_test.rb
│ │ │ └── watches_controller_test.rb
│ │ ├── cards_controller_test.rb
│ │ ├── client_configurations_controller_test.rb
│ │ ├── columns/
│ │ │ ├── cards/
│ │ │ │ └── drops/
│ │ │ │ ├── closures_controller_test.rb
│ │ │ │ ├── columns_controller_test.rb
│ │ │ │ ├── not_nows_controller_test.rb
│ │ │ │ └── streams_controller_test.rb
│ │ │ ├── left_positions_controller_test.rb
│ │ │ └── right_positions_controller_test.rb
│ │ ├── concerns/
│ │ │ ├── block_search_engine_indexing_test.rb
│ │ │ ├── current_timezone_test.rb
│ │ │ ├── request_forgery_protection_test.rb
│ │ │ └── set_platform_test.rb
│ │ ├── controller_authentication_test.rb
│ │ ├── events/
│ │ │ └── day_timeline/
│ │ │ └── columns_controller_test.rb
│ │ ├── events_controller_test.rb
│ │ ├── filters_controller_test.rb
│ │ ├── join_codes_controller_test.rb
│ │ ├── landings_controller_test.rb
│ │ ├── my/
│ │ │ ├── access_tokens_controller_test.rb
│ │ │ ├── identities_controller_test.rb
│ │ │ ├── menus_controller_test.rb
│ │ │ ├── passkey_challenges_controller_test.rb
│ │ │ ├── passkeys_controller_test.rb
│ │ │ ├── pins_controller_test.rb
│ │ │ └── timezones_controller_test.rb
│ │ ├── notifications/
│ │ │ ├── bulk_readings_controller_test.rb
│ │ │ ├── readings_controller_test.rb
│ │ │ ├── settings_controller_test.rb
│ │ │ ├── trays_controller_test.rb
│ │ │ └── unsubscribes_controller_test.rb
│ │ ├── notifications_controller_test.rb
│ │ ├── prompts/
│ │ │ ├── boards/
│ │ │ │ └── users_controller_test.rb
│ │ │ ├── cards_controller_test.rb
│ │ │ ├── tags_controller_test.rb
│ │ │ └── users_controller_test.rb
│ │ ├── public/
│ │ │ ├── boards/
│ │ │ │ ├── columns/
│ │ │ │ │ ├── closeds_controller_test.rb
│ │ │ │ │ ├── not_nows_controller_test.rb
│ │ │ │ │ └── streams_controller_test.rb
│ │ │ │ └── columns_controller_test.rb
│ │ │ ├── boards_controller_test.rb
│ │ │ └── cards_controller_test.rb
│ │ ├── qr_codes_controller_test.rb
│ │ ├── searches/
│ │ │ └── queries_controller_test.rb
│ │ ├── searches_controller_test.rb
│ │ ├── sessions/
│ │ │ ├── magic_links_controller_test.rb
│ │ │ ├── menus_controller_test.rb
│ │ │ ├── passkeys_controller_test.rb
│ │ │ └── transfers_controller_test.rb
│ │ ├── sessions_controller_test.rb
│ │ ├── signup/
│ │ │ └── completions_controller_test.rb
│ │ ├── signups_controller_test.rb
│ │ ├── tags_controller_test.rb
│ │ ├── users/
│ │ │ ├── avatars_controller_test.rb
│ │ │ ├── data_exports_controller_test.rb
│ │ │ ├── email_addresses/
│ │ │ │ └── confirmations_controller_test.rb
│ │ │ ├── email_addresses_controller_test.rb
│ │ │ ├── events_controller_test.rb
│ │ │ ├── joins_controller_test.rb
│ │ │ ├── push_subscriptions_controller_test.rb
│ │ │ ├── roles_controller_test.rb
│ │ │ └── verifications_controller_test.rb
│ │ ├── users_controller_test.rb
│ │ ├── webhooks/
│ │ │ └── activations_controller_test.rb
│ │ └── webhooks_controller_test.rb
│ ├── fixtures/
│ │ ├── accesses.yml
│ │ ├── account/
│ │ │ └── join_codes.yml
│ │ ├── accounts.yml
│ │ ├── action_text/
│ │ │ └── rich_texts.yml
│ │ ├── assignees_filters.yml
│ │ ├── assignments.yml
│ │ ├── boards.yml
│ │ ├── card/
│ │ │ └── goldnesses.yml
│ │ ├── cards.yml
│ │ ├── closures.yml
│ │ ├── columns.yml
│ │ ├── comments.yml
│ │ ├── entropies.yml
│ │ ├── events.yml
│ │ ├── exports.yml
│ │ ├── filters.yml
│ │ ├── filters_tags.yml
│ │ ├── identities.yml
│ │ ├── identity/
│ │ │ └── access_tokens.yml
│ │ ├── mentions.yml
│ │ ├── notifications.yml
│ │ ├── pins.yml
│ │ ├── reactions.yml
│ │ ├── sessions.yml
│ │ ├── taggings.yml
│ │ ├── tags.yml
│ │ ├── user/
│ │ │ └── settings.yml
│ │ ├── users.yml
│ │ ├── watches.yml
│ │ ├── webhook/
│ │ │ ├── delinquency_trackers.yml
│ │ │ └── deliveries.yml
│ │ └── webhooks.yml
│ ├── helpers/
│ │ ├── .keep
│ │ ├── action_text_rendering_test.rb
│ │ ├── application_helper_test.rb
│ │ ├── entropy_helper_test.rb
│ │ ├── excerpt_helper_test.rb
│ │ ├── hotkeys_helper_test.rb
│ │ └── html_helper_test.rb
│ ├── integration/
│ │ ├── active_storage_authorization_test.rb
│ │ ├── blob_key_traversal_test.rb
│ │ ├── card_preview_boost_count_test.rb
│ │ └── notifications_test.rb
│ ├── jobs/
│ │ ├── account/
│ │ │ ├── data_import_job_test.rb
│ │ │ └── incinerate_due_job_test.rb
│ │ ├── delete_unused_tags_job_test.rb
│ │ └── storage/
│ │ ├── materialize_job_test.rb
│ │ └── reconcile_job_test.rb
│ ├── lib/
│ │ ├── action_pack/
│ │ │ ├── passkey_test.rb
│ │ │ └── web_authn/
│ │ │ ├── authenticator/
│ │ │ │ ├── assertion_response_test.rb
│ │ │ │ ├── attestation_response_test.rb
│ │ │ │ ├── attestation_test.rb
│ │ │ │ ├── attestation_verifiers/
│ │ │ │ │ └── none_test.rb
│ │ │ │ ├── data_test.rb
│ │ │ │ └── response_test.rb
│ │ │ ├── cbor_decoder_test.rb
│ │ │ ├── cose_key_test.rb
│ │ │ ├── public_key_credential/
│ │ │ │ ├── creation_options_test.rb
│ │ │ │ └── request_options_test.rb
│ │ │ └── relying_party_test.rb
│ │ ├── rails_ext/
│ │ │ ├── action_pack_passkey_infer_name_from_aaguid_test.rb
│ │ │ ├── active_record_uuid_type_test.rb
│ │ │ ├── active_storage_analyze_job_skip_detached_test.rb
│ │ │ ├── active_storage_blob_service_url_for_direct_upload_expiry_test.rb
│ │ │ └── string_test.rb
│ │ └── web_push/
│ │ └── persistent_request_test.rb
│ ├── mailers/
│ │ ├── .keep
│ │ ├── account_mailer_test.rb
│ │ ├── export_mailer_test.rb
│ │ ├── import_mailer_test.rb
│ │ ├── magic_link_mailer_test.rb
│ │ ├── notification/
│ │ │ └── bundle_mailer_test.rb
│ │ ├── previews/
│ │ │ ├── export_mailer_preview.rb
│ │ │ ├── magic_link_mailer_preview.rb
│ │ │ ├── notification/
│ │ │ │ └── bundle_mailer_preview.rb
│ │ │ └── user_mailer_preview.rb
│ │ └── smtp_delivery_error_test.rb
│ ├── middleware/
│ │ └── account_slug_extractor_test.rb
│ ├── models/
│ │ ├── access_test.rb
│ │ ├── account/
│ │ │ ├── cancellable_test.rb
│ │ │ ├── cancellation_test.rb
│ │ │ ├── data_transfer/
│ │ │ │ ├── action_text/
│ │ │ │ │ └── rich_text_record_set_test.rb
│ │ │ │ ├── active_storage/
│ │ │ │ │ ├── blob_record_set_test.rb
│ │ │ │ │ └── file_record_set_test.rb
│ │ │ │ └── record_set_test.rb
│ │ │ ├── export_test.rb
│ │ │ ├── external_id_sequence_test.rb
│ │ │ ├── import_test.rb
│ │ │ ├── incineratable_test.rb
│ │ │ ├── join_code_test.rb
│ │ │ ├── multi_tenantable_test.rb
│ │ │ └── seedeable_test.rb
│ │ ├── account_test.rb
│ │ ├── application_platform_test.rb
│ │ ├── assignment_test.rb
│ │ ├── board/
│ │ │ ├── accessible_test.rb
│ │ │ ├── cards_test.rb
│ │ │ └── publishable_test.rb
│ │ ├── card/
│ │ │ ├── activity_spike/
│ │ │ │ └── detector_test.rb
│ │ │ ├── assignable_test.rb
│ │ │ ├── closeable_test.rb
│ │ │ ├── colored_test.rb
│ │ │ ├── commentable_test.rb
│ │ │ ├── entropic_test.rb
│ │ │ ├── eventable/
│ │ │ │ └── system_commenter_test.rb
│ │ │ ├── eventable_test.rb
│ │ │ ├── exportable_test.rb
│ │ │ ├── golden_test.rb
│ │ │ ├── messages_test.rb
│ │ │ ├── pinnable_test.rb
│ │ │ ├── postponable_test.rb
│ │ │ ├── readable_test.rb
│ │ │ ├── searchable_test.rb
│ │ │ ├── stallable_test.rb
│ │ │ ├── statuses_test.rb
│ │ │ ├── taggable_test.rb
│ │ │ ├── triageable_test.rb
│ │ │ └── watchable_test.rb
│ │ ├── card_test.rb
│ │ ├── column/
│ │ │ ├── colored_test.rb
│ │ │ └── positioned_test.rb
│ │ ├── column_limits_test.rb
│ │ ├── column_test.rb
│ │ ├── comment/
│ │ │ └── searchable_test.rb
│ │ ├── comment_test.rb
│ │ ├── concerns/
│ │ │ └── mentions_test.rb
│ │ ├── entropy_test.rb
│ │ ├── event/
│ │ │ └── description_test.rb
│ │ ├── filter/
│ │ │ └── search_test.rb
│ │ ├── filter_test.rb
│ │ ├── identity/
│ │ │ ├── access_token_test.rb
│ │ │ ├── joinable_test.rb
│ │ │ └── transferable_test.rb
│ │ ├── identity_test.rb
│ │ ├── magic_link/
│ │ │ └── code_test.rb
│ │ ├── magic_link_test.rb
│ │ ├── notification/
│ │ │ ├── bundle_test.rb
│ │ │ ├── push_target/
│ │ │ │ └── web_test.rb
│ │ │ └── pushable_test.rb
│ │ ├── notification_test.rb
│ │ ├── notifier/
│ │ │ ├── event_notifier_test.rb
│ │ │ └── mention_notifier_test.rb
│ │ ├── push/
│ │ │ └── subscription_test.rb
│ │ ├── qr_code_link_test.rb
│ │ ├── reaction_test.rb
│ │ ├── search/
│ │ │ ├── highlighter_test.rb
│ │ │ └── stemmer_test.rb
│ │ ├── search_test.rb
│ │ ├── signup/
│ │ │ └── account_name_generator_test.rb
│ │ ├── signup_test.rb
│ │ ├── ssrf_protection_test.rb
│ │ ├── storage/
│ │ │ ├── attachment_tracking_test.rb
│ │ │ ├── entry_test.rb
│ │ │ ├── no_reuse_test.rb
│ │ │ ├── total_test.rb
│ │ │ ├── totaled_test.rb
│ │ │ └── tracked_test.rb
│ │ ├── tag_test.rb
│ │ ├── time_window_parser_test.rb
│ │ ├── user/
│ │ │ ├── accessor_test.rb
│ │ │ ├── avatar_test.rb
│ │ │ ├── configurable_test.rb
│ │ │ ├── data_export_test.rb
│ │ │ ├── email_address_changeable_test.rb
│ │ │ ├── mentionable_test.rb
│ │ │ ├── named_test.rb
│ │ │ ├── notifiable_test.rb
│ │ │ ├── role_test.rb
│ │ │ ├── searcher_test.rb
│ │ │ └── settings_test.rb
│ │ ├── user_test.rb
│ │ ├── webhook/
│ │ │ ├── delinquency_tracker_test.rb
│ │ │ ├── delivery_test.rb
│ │ │ └── triggerable_test.rb
│ │ ├── webhook_test.rb
│ │ └── zip_file_test.rb
│ ├── routes_test.rb
│ ├── system/
│ │ ├── .keep
│ │ ├── back_link_navigation_test.rb
│ │ ├── markdown_paste_test.rb
│ │ └── smoke_test.rb
│ ├── test_helper.rb
│ ├── test_helpers/
│ │ ├── action_text_test_helper.rb
│ │ ├── caching_test_helper.rb
│ │ ├── card_activity_test_helper.rb
│ │ ├── card_test_helper.rb
│ │ ├── change_test_helper.rb
│ │ ├── command_test_helper.rb
│ │ ├── search_test_helper.rb
│ │ ├── session_test_helper.rb
│ │ ├── vcr_test_helper.rb
│ │ └── webauthn_test_helper.rb
│ └── webmock_ipaddr_extension.rb
├── tmp/
│ └── .keep
└── vendor/
└── javascript/
├── @hotwired--hotwire-native-bridge.js
└── @rails--request.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/CLAUDE.md
================================================
@../AGENTS.md
================================================
FILE: .dockerignore
================================================
# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
# Ignore git directory.
/.git/
# Ignore bundler config.
/.bundle
# Ignore documentation
/docs/
/README.md
/CLAUDE.md
/AGENTS.md
/STYLE.md
/CONTRIBUTING.md
# Ignore all environment files (except templates).
/.env*
!/.env*.erb
# Ignore all default key files.
/config/master.key
/config/credentials/*.key
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/.keep
# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/.keep
# Ignore assets.
/node_modules/
/app/assets/builds/*
!/app/assets/builds/.keep
/public/assets
================================================
FILE: .gitattributes
================================================
# See https://git-scm.com/docs/gitattributes for more about git attribute files.
# Mark the database schema as having been generated.
db/schema.rb linguist-generated
# Mark any vendored files as having been vendored.
vendor/* linguist-vendored
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Features, Bug Reports, Questions
url: https://github.com/basecamp/fizzy/discussions/new/choose
about: Please use the discussions area to report issues or ask quest
================================================
FILE: .github/ISSUE_TEMPLATE/preapproved.md
================================================
---
name: Pre-Discussed and Approved Topics
about: |-
For topics already discussed and approved in the GitHub Discussions section.
---
** PLEASE START A DISCUSSION INSTEAD OF OPENING AN ISSUE **
** For more details see CONTRIBUTING.md **
================================================
FILE: .github/dependabot.yml
================================================
version: 2
registries:
github-basecamp:
type: git
url: https://github.com
username: x-access-token
password: ${{secrets.FIZZY_GH_TOKEN}}
updates:
- package-ecosystem: bundler
registries:
- github-basecamp
directory: "/"
insecure-external-code-execution: allow # zizmor: ignore[dependabot-execution] -- required for Bundler to resolve gems from the private github-basecamp registry
open-pull-requests-limit: 10
vendor: false
groups:
development-dependencies:
dependency-type: "development"
schedule:
interval: weekly
cooldown:
default-days: 7
semver-major-days: 14
- package-ecosystem: github-actions
directory: "/"
groups:
github-actions:
patterns:
- "*"
schedule:
interval: weekly
open-pull-requests-limit: 10
cooldown:
default-days: 7
================================================
FILE: .github/workflows/ci-checks.yml
================================================
name: Checks
on:
pull_request:
permissions:
contents: read
jobs:
gemfile-drift:
name: Gemfile drift
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Check for lockfile drift
run: bin/bundle-drift check
security:
name: Security
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Gem audit
run: bin/bundler-audit check --update
- name: Importmap audit
run: bin/importmap audit
- name: Brakeman audit
run: bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Lint code for consistent style
run: bin/rubocop
zizmor:
name: GitHub Actions audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
with:
advanced-security: false
================================================
FILE: .github/workflows/ci-oss.yml
================================================
name: CI (OSS)
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
jobs:
test:
if: github.event.pull_request.head.repo.full_name != github.repository
uses: ./.github/workflows/test.yml
with:
saas: false
================================================
FILE: .github/workflows/ci-saas.yml
================================================
name: CI (SaaS)
on:
push:
permissions:
contents: read
jobs:
test_oss:
name: Test (OSS)
uses: ./.github/workflows/test.yml
with:
saas: false
test_saas:
name: Test (SaaS)
uses: ./.github/workflows/test.yml
with:
saas: true
secrets:
FIZZY_GH_TOKEN: ${{ secrets.FIZZY_GH_TOKEN }}
================================================
FILE: .github/workflows/dependabot-sync-saas-lockfile.yml
================================================
name: Sync Gemfile.saas.lock
on:
push:
branches:
- "dependabot/bundler/**"
paths:
- Gemfile.lock
permissions:
contents: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 # zizmor: ignore[artipacked] -- credentials needed for git push
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: .ruby-version
- name: Forward Gemfile.lock changes to Gemfile.saas.lock
run: bin/bundle-drift forward
- name: Commit updated lockfile
run: |
git add Gemfile.saas.lock
if ! git diff --cached --quiet; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git commit -m "Sync Gemfile.saas.lock"
git push
fi
================================================
FILE: .github/workflows/publish-image.yml
================================================
name: Build and publish container image to GHCR
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
concurrency:
group: publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
IMAGE_DESCRIPTION: Fizzy is Kanban as it should be. Not as it has been.
SOURCE_URL: https://github.com/${{ github.repository }}
jobs:
build:
name: Build and push image (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
permissions:
contents: read
packages: write
id-token: write
attestations: write
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
platform: linux/amd64
arch: amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
arch: arm64
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Log in to GHCR
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute canonical image name (lowercase)
id: vars
shell: bash
run: |
set -eu
IMAGE_REF="${IMAGE_NAME:-$GITHUB_REPOSITORY}"
CANONICAL_IMAGE="${REGISTRY}/${IMAGE_REF,,}"
echo "canonical=${CANONICAL_IMAGE}" >> "$GITHUB_OUTPUT"
- name: Extract Docker metadata (tags, labels) with arch suffix
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: ${{ steps.vars.outputs.canonical }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short,prefix=sha-
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
flavor: |
latest=false
suffix=-${{ matrix.arch }}
labels: |
org.opencontainers.image.source=${{ env.SOURCE_URL }}
- name: Build and push (${{ matrix.platform }})
id: build
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: .
file: Dockerfile
build-args: |
OCI_SOURCE=${{ env.SOURCE_URL }}
OCI_DESCRIPTION=${{ env.IMAGE_DESCRIPTION }}
platforms: ${{ matrix.platform }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,scope=${{ matrix.platform }},mode=max
sbom: false
provenance: false
- name: Attest image provenance (per-arch)
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-name: ${{ steps.vars.outputs.canonical }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: false
manifest:
name: Create multi-arch manifest and sign
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
packages: write
id-token: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Set up Docker Buildx (for imagetools)
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Log in to GHCR
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute canonical image name (lowercase)
id: vars
shell: bash
run: |
set -eu
IMAGE_REF="${IMAGE_NAME:-$GITHUB_REPOSITORY}"
CANONICAL_IMAGE="${REGISTRY}/${IMAGE_REF,,}"
echo "canonical=${CANONICAL_IMAGE}" >> "$GITHUB_OUTPUT"
- name: Compute base tags (no suffix)
id: meta
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
with:
images: ${{ steps.vars.outputs.canonical }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short,prefix=sha-
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
flavor: |
latest=false
labels: |
org.opencontainers.image.source=${{ env.SOURCE_URL }}
- name: Create multi-arch manifests
shell: bash
env:
TAGS: ${{ steps.meta.outputs.tags }}
run: |
set -eu
tags="$TAGS"
echo "Creating manifests for tags:"
printf '%s\n' "$tags"
while IFS= read -r tag; do
[ -z "$tag" ] && continue
echo "Creating manifest for $tag"
src_tag="$tag"
if [[ "$tag" == *:latest && "${GITHUB_REF}" == refs/tags/* ]]; then
ref="${GITHUB_REF#refs/tags/}"
src_tag="${tag%:latest}:$ref"
fi
if [ -n "${IMAGE_DESCRIPTION:-}" ]; then
docker buildx imagetools create \
--tag "$tag" \
--annotation "index:org.opencontainers.image.description=${IMAGE_DESCRIPTION}" \
"${src_tag}-amd64" \
"${src_tag}-arm64"
else
docker buildx imagetools create \
--tag "$tag" \
"${src_tag}-amd64" \
"${src_tag}-arm64"
fi
done <<< "$tags"
- name: Install Cosign
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
- name: Cosign sign all tags (keyless OIDC)
shell: bash
env:
TAGS: ${{ steps.meta.outputs.tags }}
run: |
set -eu
tags="$TAGS"
printf '%s\n' "$tags"
while IFS= read -r tag; do
[ -z "$tag" ] && continue
echo "Signing $tag"
cosign sign --yes "$tag"
done <<< "$tags"
================================================
FILE: .github/workflows/test.yml
================================================
name: Test
on:
workflow_call:
inputs:
saas:
type: boolean
required: true
secrets:
FIZZY_GH_TOKEN:
required: false
permissions:
contents: read
jobs:
test:
name: Tests (${{ matrix.mode }})
runs-on: ubuntu-latest
strategy:
matrix:
include:
- mode: SQLite
db_adapter: sqlite
- mode: MySQL
db_adapter: mysql
services:
mysql:
image: mysql:8.0
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: fizzy_test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=3
env:
RAILS_ENV: test
DATABASE_ADAPTER: ${{ matrix.db_adapter }}
${{ inputs.saas && 'SAAS' || 'SAAS_DISABLED' }}: ${{ inputs.saas && '1' || '' }}
BUNDLE_GEMFILE: ${{ inputs.saas && 'Gemfile.saas' || 'Gemfile' }}
MYSQL_HOST: 127.0.0.1
MYSQL_PORT: 3306
MYSQL_USER: root
FIZZY_DB_HOST: 127.0.0.1
FIZZY_DB_PORT: 3306
BUNDLE_GITHUB__COM: ${{ inputs.saas && format('x-access-token:{0}', secrets.FIZZY_GH_TOKEN) || '' }} # zizmor: ignore[secrets-outside-env]
steps:
- name: Install system packages
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y libsqlite3-0 libvips curl ffmpeg
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: .ruby-version
bundler-cache: true
- name: Run tests
run: bin/rails db:setup test
- name: Run system tests
run: bin/rails test:system
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore all environment files (except templates).
/.env*
!/.env*.erb
# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
*.log
# Ignore pidfiles, but keep the directory.
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
# Ignore storage (uploaded files in development and any SQLite databases).
/storage/*
!/storage/.keep
/tmp/storage/*
!/tmp/storage/
!/tmp/storage/.keep
/data
*.sqlite3
*.sqlite3_*
/public/assets
# Ignore master key for decrypting credentials and more.
/config/master.key
/config/credentials/*.key
.DS_Store
================================================
FILE: .gitleaks.toml
================================================
[extend]
useDefault = true
[allowlist]
paths = [
'''log''',
'''tmp''',
'''.*\.yml\.enc''',
'''docs/''',
'''test/''',
]
[[rules]]
id = "basecamp-integration-url"
description = "Basecamp Integration URL"
regex = '''https://[^\s]*?([0-9a-fA-F]{16,})'''
[rules.allowlist]
regexTarget = "match"
regexes = ['''github\.com''']
================================================
FILE: .gitleaksignore
================================================
d8463077:gems/fizzy-saas/bin/setup:generic-api-key:54
c4073c1c:app/models/integration/basecamp.rb:generic-api-key:3
c4073c1c:app/models/integration/basecamp.rb:generic-api-key:4
================================================
FILE: .mise.toml
================================================
[settings]
idiomatic_version_file_enable_tools = ["ruby"]
[env]
PROMETHEUS_EXPORTER_URL = "http://127.0.0.1:9306/metrics"
================================================
FILE: .rubocop.yml
================================================
# Omakase Ruby styling for Rails
inherit_gem: { rubocop-rails-omakase: rubocop.yml }
# Overwrite or add rules to create your own house style
#
# # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
# Layout/SpaceInsideArrayLiteralBrackets:
# Enabled: false
AllCops:
Exclude:
- 'db/migrate/**/*'
- 'db/schema*.rb'
- 'saas/db/migrate/**/*'
- 'saas/db/saas_schema.rb'
================================================
FILE: .ruby-version
================================================
3.4.7
================================================
FILE: AGENTS.md
================================================
# Fizzy
This file provides guidance to AI coding agents working with this repository.
## What is Fizzy?
Fizzy is a collaborative project management and issue tracking application built by 37signals/Basecamp. It's a kanban-style tool for teams to create and manage cards (tasks/issues) across boards, organize work into columns representing workflow stages, and collaborate via comments, mentions, and assignments.
## Development Commands
### Setup and Server
```bash
bin/setup # Initial setup (installs gems, creates DB, loads schema)
bin/dev # Start development server (runs on port 3006)
```
Development URL: http://fizzy.localhost:3006
Login with: david@example.com (development fixtures), password will appear in the browser console
### Testing
```bash
bin/rails test # Run unit tests (fast)
bin/rails test test/path/file_test.rb # Run single test file
bin/rails test:system # Run system tests (Capybara + Selenium)
bin/ci # Run full CI suite (style, security, tests)
# For parallel test execution issues, use:
PARALLEL_WORKERS=1 bin/rails test
```
CI pipeline (`bin/ci`) runs:
1. Rubocop (style)
2. Bundler audit (gem security)
3. Importmap audit
4. Brakeman (security scan)
5. Application tests
6. System tests
### Database
```bash
bin/rails db:fixtures:load # Load fixture data
bin/rails db:migrate # Run migrations
bin/rails db:reset # Drop, create, and load schema
```
### Other Utilities
```bash
bin/rails dev:email # Toggle letter_opener for email preview
bin/jobs # Manage Solid Queue jobs
bin/kamal deploy # Deploy (requires 1Password CLI for secrets)
```
## Deploy
Default branch: `main`
Pre-deploy: `bin/rails saas:enable`
Deploy: `bin/kamal deploy -d <destination>`
Destinations: production, staging, beta, beta1, beta2, beta3, beta4
Note: `beta` is a template requiring `BETA_NUMBER` env var; typical targets are `beta1`-`beta4`.
## Architecture Overview
### Multi-Tenancy (URL-Based)
Fizzy uses **URL path-based multi-tenancy**:
- Each Account (tenant) has a unique `external_account_id` (7+ digits)
- URLs are prefixed: `/{account_id}/boards/...`
- Middleware (`AccountSlug::Extractor`) extracts the account ID from the URL and sets `Current.account`
- The slug is moved from `PATH_INFO` to `SCRIPT_NAME`, making Rails think it's "mounted" at that path
- All models include `account_id` for data isolation
- Background jobs automatically serialize and restore account context
**Key insight**: This architecture allows multi-tenancy without subdomains or separate databases, making local development and testing simpler.
### Authentication & Authorization
**Passwordless magic link authentication**:
- Global `Identity` (email-based) can have `Users` in multiple Accounts
- Users belong to an Account and have roles: owner, admin, member, system
- Sessions managed via signed cookies
- Board-level access control via `Access` records
### Core Domain Models
**Account** → The tenant/organization
- Has users, boards, cards, tags, webhooks
- Has entropy configuration for auto-postponement
**Identity** → Global user (email)
- Can have Users in multiple Accounts
- Session management tied to Identity
**User** → Account membership
- Belongs to Account and Identity
- Has role (owner/admin/member/system)
- Board access via explicit `Access` records
**Board** → Primary organizational unit
- Has columns for workflow stages
- Can be "all access" or selective
- Can be published publicly with shareable key
**Card** → Main work item (task/issue)
- Sequential number within each Account
- Rich text description and attachments
- Lifecycle: triage → columns → closed/not_now
- Automatically postpones after inactivity ("entropy")
**Event** → Records all significant actions
- Polymorphic association to changed object
- Drives activity timeline, notifications, webhooks
- Has JSON `particulars` for action-specific data
### Entropy System
Cards automatically "postpone" (move to "not now") after inactivity:
- Account-level default entropy period
- Board-level entropy override
- Prevents endless todo lists from accumulating
- Configurable via Account/Board settings
### UUID Primary Keys
All tables use UUIDs (UUIDv7 format, base36-encoded as 25-char strings):
- Custom fixture UUID generation maintains deterministic ordering for tests
- Fixtures are always "older" than runtime records
- `.first`/`.last` work correctly in tests
### Background Jobs (Solid Queue)
Database-backed job queue (no Redis):
- Custom `FizzyActiveJobExtensions` prepended to ActiveJob
- Jobs automatically capture/restore `Current.account`
- Mission Control::Jobs for monitoring
Key recurring tasks (via `config/recurring.yml`):
- Deliver bundled notifications (every 30 min)
- Auto-postpone stale cards (hourly)
- Cleanup jobs for expired links, deliveries
### Sharded Full-Text Search
16-shard MySQL full-text search instead of Elasticsearch:
- Shards determined by account ID hash (CRC32)
- Search records denormalized for performance
- Models in `app/models/search/`
### Imports and exports
Allow people to move between OSS and SAAS Fizzy instances:
- Exports/Imports can be written to/read from local or S3 storage depending on the config of the instance (both must be supported)
- Must be able to handle very large ZIP files (500+GB)
- Models in `app/models/account/data_transfer/`, `app/models/zip_file`
## Tools
### Chrome MCP (Local Dev)
URL: `http://fizzy.localhost:3006`
Login: david@example.com (passwordless magic link auth - check rails console for link)
Use Chrome MCP tools to interact with the running dev app for UI testing and debugging.
## Coding style
@STYLE.md
================================================
FILE: CONTRIBUTING.md
================================================
# How to contribute to Fizzy
Fizzy uses GitHub
[discussions](https://github.com/basecamp/fizzy/discussions) to track
feature requests and questions, rather than [the issue
tracker](https://github.com/basecamp/fizzy/issues). If you're considering
opening an issue or pull request, please open a discussion instead.
Whenever a discussion leads to an actionable and well-understood task, we'll
move it to the issue tracker where it can be worked on.
This is a little different than how some other projects work, but it makes it
easier for us to triage and prioritise the work. It also means that the open
issues all represent agreed-upon tasks that are either being worked on, or are
ready to be worked on.
This should also make it easier to see what's in progress, and to find
something to work on if you'd like to do so.
## What this means in practice
### If you'd like to contribute to the code...
1. If you're interested in working on one of the open issues, please do! We are
grateful for the help!
2. You'll want to make sure someone else isn't already working on the same
issue. If they are, it will be tagged "in progress" and/or it should be clear
from the comments. When in doubt, you can always comment on the issue to ask.
3. Similarly, if you need any help or guidance on the issue, please comment on
the issue as you go, and we'll do our best to help.
4. When you have something ready for review or collaboration, open a PR.
### If you've found a bug...
1. If you don't have steps to reproduce the problem, or you're not certain it's a
bug, open a discussion.
2. If you have steps to reproduce, open an issue.
### If you have an idea for a feature...
1. Open a discussion.
### If you have a question, or are having trouble with configuration...
1. Open a discussion.
Hopefully this process makes it easier for everyone to be involved. Thanks for
helping! ❤️
================================================
FILE: Dockerfile
================================================
# syntax=docker/dockerfile:1
# check=error=true
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t fizzy .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name fizzy fizzy
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.4.7
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
# Rails app lives here
WORKDIR /rails
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 libssl-dev && \
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Set production environment variables and enable jemalloc for reduced memory usage and latency.
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test" \
LD_PRELOAD="/usr/local/lib/libjemalloc.so"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application gems
COPY Gemfile Gemfile.lock vendor ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
bundle exec bootsnap precompile -j 1 --gemfile
# Copy application code
COPY . .
# Precompile bootsnap code for faster boot times.
# -j 1 disable parallel compilation to avoid a QEMU bug: https://github.com/rails/bootsnap/issues/495
RUN bundle exec bootsnap precompile -j 1 app/ lib/
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage for app image
FROM base
# Image metadata
ARG OCI_DESCRIPTION
LABEL org.opencontainers.image.description="${OCI_DESCRIPTION}"
ARG OCI_SOURCE
LABEL org.opencontainers.image.source="${OCI_SOURCE}"
LABEL org.opencontainers.image.licenses="O'Saasy"
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash
USER 1000:1000
# Copy built artifacts: gems, application
COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --chown=rails:rails --from=build /rails /rails
# Entrypoint prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start server via Thruster by default, this can be overwritten at runtime
EXPOSE 80
CMD ["./bin/thrust", "./bin/rails", "server"]
================================================
FILE: Gemfile
================================================
source "https://rubygems.org"
git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" }
gem "rails", github: "rails/rails", branch: "main"
# Assets & front end
gem "importmap-rails"
gem "propshaft"
gem "stimulus-rails"
gem "turbo-rails", github: "hotwired/turbo-rails", branch: "offline-cache"
# Deployment and drivers
gem "bootsnap", require: false
gem "kamal", require: false
gem "puma", ">= 5.0"
gem "solid_cable", ">= 3.0"
gem "solid_cache", "~> 1.0"
gem "solid_queue", "~> 1.3"
gem "sqlite3", ">= 2.0"
gem "thruster", require: false
gem "trilogy", "~> 2.10"
# Features
gem "bcrypt", "~> 3.1.7"
gem "geared_pagination", "~> 1.2"
gem "rqrcode"
gem "rouge"
gem "jbuilder"
gem "lexxy", "0.9.0.beta"
gem "image_processing", "~> 1.14"
gem "platform_agent"
gem "aws-sdk-s3", require: false
gem "web-push"
gem "net-http-persistent"
gem "zip_kit"
gem "mittens"
gem "useragent", bc: "useragent"
# Operations
gem "autotuner"
gem "mission_control-jobs"
gem "stackprof"
gem "benchmark" # indirect dependency, being removed from Ruby 3.5 stdlib so here to quash warnings
group :development, :test do
gem "brakeman", require: false
gem "bundler-audit", require: false
gem "debug"
gem "faker"
gem "letter_opener"
gem "rack-mini-profiler"
gem "rubocop-rails-omakase", require: false
end
group :development do
gem "web-console", github: "rails/web-console"
end
group :test do
gem "capybara"
gem "selenium-webdriver"
gem "webmock"
gem "vcr"
gem "mocha"
end
================================================
FILE: Gemfile.saas
================================================
# This Gemfile extends the base Gemfile with SaaS-specific dependencies
eval_gemfile "Gemfile"
git_source(:bc) { |repo| "https://github.com/basecamp/#{repo}" }
gem "activeresource", require: "active_resource"
gem "actionpack-xml_parser" # needed by queenbee for XML request body parsing
gem "queenbee", bc: "queenbee-plugin"
gem "fizzy-saas", path: "saas"
gem "console1984", bc: "console1984"
gem "audits1984", bc: "audits1984", branch: "flavorjones/coworker-api"
# Native push notifications (iOS/Android)
gem "action_push_native"
# Telemetry
gem "rails_structured_logging", bc: "rails-structured-logging"
gem "sentry-ruby"
gem "sentry-rails"
gem "yabeda"
gem "yabeda-actioncable"
gem "yabeda-activejob", github: "basecamp/yabeda-activejob", branch: "bulk-and-scheduled-jobs"
gem "yabeda-gc"
gem "yabeda-http_requests"
gem "yabeda-prometheus-mmap"
gem "yabeda-puma-plugin"
gem "yabeda-rails"
gem "webrick" # required for yabeda-prometheus metrics server
gem "prometheus-client-mmap", "~> 1.3"
gem "gvltools"
================================================
FILE: LICENSE.md
================================================
# O'Saasy License Agreement
Copyright © 2025, 37signals LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
2. No licensee or downstream recipient may use the Software (including any modified or derivative versions) to directly compete with the original Licensor by offering it to third parties as a hosted, managed, or Software-as-a-Service (SaaS) product or cloud service where the primary value of the service is the functionality of the Software itself.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Fizzy
This is the source code of [Fizzy](https://fizzy.do/), the Kanban tracking tool for issues and ideas by [37signals](https://37signals.com).
## Running your own Fizzy instance
If you want to run your own Fizzy instance, but don't need to change its code, you can use our pre-built Docker image.
You'll need access to a server on which you can run Docker, and you'll need to configure some options to customize your installation.
You can find the details of how to do a Docker-based deployment in our [Docker deployment guide](docs/docker-deployment.md).
If you want more flexibility to customize your Fizzy installation by changing its code, and deploy those changes to your server, then we recommend you deploy Fizzy with Kamal. You can find a complete walkthrough of doing that in our [Kamal deployment guide](docs/kamal-deployment.md).
## Development
You are welcome -- and encouraged -- to modify Fizzy to your liking.
Please see our [Development guide](docs/development.md) for how to get Fizzy set up for local development.
## Contributing
We welcome contributions! Please read our [style guide](STYLE.md) before submitting code.
## License
Fizzy is released under the [O'Saasy License](LICENSE.md).
================================================
FILE: Rakefile
================================================
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require_relative 'config/application'
Rails.application.load_tasks
================================================
FILE: STYLE.md
================================================
# Style
We aim to write code that is a pleasure to read, and we have a lot of opinions about how to do it well. Writing great code is an essential part of our programming culture, and we deliberately set a high bar for every code change anyone contributes. We care about how code reads, how code looks, and how code makes you feel when you read it.
We love discussing code. If you have questions about how to write something, or if you detect some smell you are not quite sure how to solve, please ask away to other programmers. A Pull Request is a great way to do this.
When writing new code, unless you are very familiar with our approach, try to find similar code elsewhere to look for inspiration.
## Conditional returns
In general, we prefer to use expanded conditionals over guard clauses.
```ruby
# Bad
def todos_for_new_group
ids = params.require(:todolist)[:todo_ids]
return [] unless ids
@bucket.recordings.todos.find(ids.split(","))
end
# Good
def todos_for_new_group
if ids = params.require(:todolist)[:todo_ids]
@bucket.recordings.todos.find(ids.split(","))
else
[]
end
end
```
This is because guard clauses can be hard to read, especially when they are nested.
As an exception, we sometimes use guard clauses to return early from a method:
* When the return is right at the beginning of the method.
* When the main method body is not trivial and involves several lines of code.
```ruby
def after_recorded_as_commit(recording)
return if recording.parent.was_created?
if recording.was_created?
broadcast_new_column(recording)
else
broadcast_column_change(recording)
end
end
```
## Methods ordering
We order methods in classes in the following order:
1. `class` methods
2. `public` methods with `initialize` at the top.
3. `private` methods
## Invocation order
We order methods vertically based on their invocation order. This helps us to understand the flow of the code.
```ruby
class SomeClass
def some_method
method_1
method_2
end
private
def method_1
method_1_1
method_1_2
end
def method_1_1
# ...
end
def method_1_2
# ...
end
def method_2
method_2_1
method_2_2
end
def method_2_1
# ...
end
def method_2_2
# ...
end
end
```
## To bang or not to bang
Should I call a method `do_something` or `do_something!`?
As a general rule, we only use `!` for methods that have a correspondent counterpart without `!`. In particular, we don’t use `!` to flag destructive actions. There are plenty of destructive methods in Ruby and Rails that do not end with `!`.
## Visibility modifiers
We don't add a newline under visibility modifiers, and we indent the content under them.
```ruby
class SomeClass
def some_method
# ...
end
private
def some_private_method_1
# ...
end
def some_private_method_2
# ...
end
end
```
If a module only has private methods, we mark it `private` at the top and add an extra new line after but don't indent.
```ruby
module SomeModule
private
def some_private_method
# ...
end
end
```
## CRUD controllers
We model web endpoints as CRUD operations on resources (REST). When an action doesn't map cleanly to a standard CRUD verb, we introduce a new resource rather than adding custom actions.
```ruby
# Bad
resources :cards do
post :close
post :reopen
end
# Good
resources :cards do
resource :closure
end
```
## Controller and model interactions
In general, we favor a [vanilla Rails](https://dev.37signals.com/vanilla-rails-is-plenty/) approach with thin controllers directly invoking a rich domain model. We don't use services or other artifacts to connect the two.
Invoking plain Active Record operations is totally fine:
```ruby
class Cards::CommentsController < ApplicationController
def create
@comment = @card.comments.create!(comment_params)
end
end
```
For more complex behavior, we prefer clear, intention-revealing model APIs that controllers call directly:
```ruby
class Cards::GoldnessesController < ApplicationController
def create
@card.gild
end
end
```
When justified, it is fine to use services or form objects, but don't treat those as special artifacts:
```ruby
Signup.new(email_address: email_address).create_identity
```
## Run async operations in jobs
As a general rule, we write shallow job classes that delegate the logic itself to domain models:
* We typically use the suffix `_later` to flag methods that enqueue a job.
* A common scenario is having a model class that enqueues a job that, when executed, invokes some method in that same class. In this case, we use the suffix `_now` for the regular synchronous method.
```ruby
module Event::Relaying
extend ActiveSupport::Concern
included do
after_create_commit :relay_later
end
def relay_later
Event::RelayJob.perform_later(self)
end
def relay_now
# ...
end
end
class Event::RelayJob < ApplicationJob
def perform(event)
event.relay_now
end
end
```
================================================
FILE: app/assets/images/.keep
================================================
================================================
FILE: app/assets/stylesheets/_global.css
================================================
@layer reset, base, components, modules, utilities, native, platform;
:root {
/* Insets - The mobile apps may inject their own custom insets based on native elements on screen, like a floating navigation */
--custom-safe-inset-top: var(--injected-safe-inset-top, env(safe-area-inset-top, 0px));
--custom-safe-inset-right: var(--injected-safe-inset-right, env(safe-area-inset-right, 0px));
--custom-safe-inset-bottom: var(--injected-safe-inset-bottom, env(safe-area-inset-bottom, 0px));
--custom-safe-inset-left: var(--injected-safe-inset-left, env(safe-area-inset-left, 0px));
/* Spacing */
--inline-space: 1ch;
--inline-space-half: calc(var(--inline-space) / 2);
--inline-space-double: calc(var(--inline-space) * 2);
--block-space: 1rem;
--block-space-half: calc(var(--block-space) / 2);
--block-space-double: calc(var(--block-space) * 2);
/* Text */
--font-sans: "Adwaita Sans", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Fizzy", "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
--font-serif: ui-serif, serif;
--font-mono: ui-monospace, monospace;
--text-xx-small: 0.55rem;
--text-x-small: 0.75rem;
--text-small: 0.85rem;
--text-normal: 1rem;
--text-medium: 1.1rem;
--text-large: 1.5rem;
--text-x-large: 1.8rem;
--text-xx-large: 2.5rem;
@media (max-width: 639px) {
--text-xx-small: 0.65rem;
--text-x-small: 0.85rem;
--text-small: 0.95rem;
--text-normal: 1.1rem;
--text-medium: 1.2rem;
--text-large: 1.5rem;
--text-x-large: 1.8rem;
--text-xx-large: 2.5rem;
}
/* Borders */
--border: 1px solid var(--color-ink-lighter);
/* Shadows */
--shadow: 0 0 0 1px oklch(var(--lch-black) / 5%),
0 0.2em 0.2em oklch(var(--lch-black) / 5%),
0 0.4em 0.4em oklch(var(--lch-black) / 5%),
0 0.8em 0.8em oklch(var(--lch-black) / 5%);
/* Components */
--btn-size: 2.65em;
--footer-height: 2.65rem;
--tray-size: clamp(12rem, 25dvw, 24rem);
/* Focus rings for keyboard navigation */
--focus-ring-color: var(--color-link);
--focus-ring-offset: 1px;
--focus-ring-size: 2px;
--focus-ring: var(--focus-ring-size) solid var(--focus-ring-color);
/* Dialogs */
--dialog-duration: 150ms;
/* Easing functions from https://easingwizard.com/ */
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-out-overshoot: cubic-bezier(0.25, 1.75, 0.5, 1);
--ease-out-overshoot-subtle: cubic-bezier(0.25, 1.25, 0.5, 1);
@media (max-width: 799px) {
--tray-size: var(--footer-height);
}
/* Layout */
--main-padding: clamp(var(--inline-space), 3vw, calc(var(--inline-space) * 3));
--main-width: 1400px;
/* Z-index */
--z-events-column-header: 1;
--z-events-day-header: 3;
--z-popup: 10;
--z-nav: 20;
--z-flash: 30;
--z-tooltip: 40;
--z-bar: 50;
--z-tray: 51;
--z-welcome: 52;
--z-nav-open: 100;
/* OKLCH colors: Fixed */
--lch-black: 0% 0 0;
--lch-white: 100% 0 0;
/* OKLCH colors: Light mode */
--lch-canvas: var(--lch-white);
--lch-ink-inverted: var(--lch-white);
--lch-ink-darkest: 26% 0.05 264;
--lch-ink-darker: 40% 0.026 262;
--lch-ink-dark: 56% 0.014 260;
--lch-ink-medium: 66% 0.008 258;
--lch-ink-light: 84% 0.005 256;
--lch-ink-lighter: 92% 0.003 254;
--lch-ink-lightest: 96% 0.002 252;
--lch-uncolor-darkest: 26% 0.018 40;
--lch-uncolor-darker: 40.04% 0.0376 50.06;
--lch-uncolor-dark: 57.09% 0.0676 60.5;
--lch-uncolor-medium: 66% 0.0944 71.46;
--lch-uncolor-light: 83.97% 0.0457 80.84;
--lch-uncolor-lighter: 92% 0.014 90;
--lch-uncolor-lightest: 96% 0.012 100;
--lch-red-darkest: 26% 0.105 34;
--lch-red-darker: 40% 0.154 36;
--lch-red-dark: 59% 0.19 38;
--lch-red-medium: 66% 0.204 40;
--lch-red-light: 84.08% 0.0837 41.96;
--lch-red-lighter: 92% 0.03 44;
--lch-red-lightest: 96% 0.013 46;
--lch-yellow-darkest: 26% 0.0729 40;
--lch-yellow-darker: 40% 0.12 50;
--lch-yellow-dark: 58% 0.156 60;
--lch-yellow-medium: 74% 0.184 70;
--lch-yellow-light: 84% 0.12 80;
--lch-yellow-lighter: 92% 0.076 90;
--lch-yellow-lightest: 96% 0.034 100;
--lch-lime-darkest: 26% 0.064 109;
--lch-lime-darker: 40% 0.101 110;
--lch-lime-dark: 56.5% 0.142 111;
--lch-lime-medium: 68% 0.176 113.11;
--lch-lime-light: 83.92% 0.0927 113.6;
--lch-lime-lighter: 92% 0.046 114;
--lch-lime-lightest: 96% 0.034 115;
--lch-green-darkest: 26% 0.071 149;
--lch-green-darker: 40% 0.12 148;
--lch-green-dark: 55% 0.162 147;
--lch-green-medium: 66% 0.208 146;
--lch-green-light: 83.92% 0.0772 145.06;
--lch-green-lighter: 92% 0.044 144;
--lch-green-lightest: 96% 0.022 143;
--lch-aqua-darkest: 26% 0.059 214;
--lch-aqua-darker: 40% 0.093 212;
--lch-aqua-dark: 55.5% 0.122 210;
--lch-aqua-medium: 66% 0.152 208;
--lch-aqua-light: 83.88% 0.0555 206.02;
--lch-aqua-lighter: 92% 0.02 204;
--lch-aqua-lightest: 96% 0.012 202;
--lch-blue-darkest: 26% 0.126 264;
--lch-blue-darker: 40% 0.166 262;
--lch-blue-dark: 57.02% 0.1895 260.46;
--lch-blue-medium: 66% 0.196 257.82;
--lch-blue-light: 84.04% 0.0719 255.29;
--lch-blue-lighter: 92% 0.026 254;
--lch-blue-lightest: 96% 0.016 252;
--lch-violet-darkest: 26% 0.148 292;
--lch-violet-darker: 40% 0.2 290;
--lch-violet-dark: 58% 0.216 287.6;
--lch-violet-medium: 66% 0.206 285.52;
--lch-violet-light: 84.08% 0.0791 283.47;
--lch-violet-lighter: 92% 0.03 282;
--lch-violet-lightest: 96% 0.015 280;
--lch-purple-darkest: 26% 0.131 314;
--lch-purple-darker: 40% 0.178 312;
--lch-purple-dark: 58% 0.21 310;
--lch-purple-medium: 66% 0.258 308;
--lch-purple-light: 84.09% 0.0778 305.77;
--lch-purple-lighter: 92% 0.03 304;
--lch-purple-lightest: 96% 0.019 302;
--lch-pink-darkest: 26% 0.12 348;
--lch-pink-darker: 40% 0.16 346;
--lch-pink-dark: 59% 0.188 344;
--lch-pink-medium: 71.8% 0.2008 342;
--lch-pink-light: 84.04% 0.0737 340;
--lch-pink-lighter: 92% 0.03 338;
--lch-pink-lightest: 96% 0.02 336;
/* Colors: Named */
--color-black: oklch(var(--lch-black));
--color-white: oklch(var(--lch-white));
--color-ink: oklch(var(--lch-ink-darkest));
--color-ink-darkest: oklch(var(--lch-ink-darkest));
--color-ink-darker: oklch(var(--lch-ink-darker));
--color-ink-dark: oklch(var(--lch-ink-dark));
--color-ink-medium: oklch(var(--lch-ink-medium));
--color-ink-light: oklch(var(--lch-ink-light));
--color-ink-lighter: oklch(var(--lch-ink-lighter));
--color-ink-lightest: oklch(var(--lch-ink-lightest));
--color-ink-inverted: oklch(var(--lch-ink-inverted));
/* Colors: Abstractions */
--color-canvas: oklch(var(--lch-canvas));
--color-negative: oklch(var(--lch-red-dark));
--color-positive: oklch(var(--lch-green-dark));
--color-link: oklch(var(--lch-blue-dark));
--color-selected-light: oklch(var(--lch-blue-lightest));
--color-selected: oklch(var(--lch-blue-lighter));
--color-selected-dark: oklch(var(--lch-blue-light));
--color-highlight: oklch(var(--lch-yellow-lighter));
--color-marker: oklch(var(--lch-red-medium));
--color-terminal-bg: oklch(98% 0.002 252);
--color-terminal-text: var(--color-ink);
--color-terminal-text-light: var(--color-ink-lighter);
--color-golden: oklch(89.1% 0.178 95.7);
--color-maybe: oklch(var(--lch-blue-medium));
/* Colors: Cards */
--color-card-default: oklch(var(--lch-blue-dark));
--color-card-complete: var(--color-ink-darker);
--color-card-1: oklch(var(--lch-ink-medium));
--color-card-2: oklch(var(--lch-uncolor-medium));
--color-card-3: oklch(var(--lch-yellow-medium));
--color-card-4: oklch(var(--lch-lime-medium));
--color-card-5: oklch(var(--lch-aqua-medium));
--color-card-6: oklch(var(--lch-violet-medium));
--color-card-7: oklch(var(--lch-purple-medium));
--color-card-8: oklch(var(--lch-pink-medium));
/* Colors: Highlighter */
--highlight-1: rgb(136, 118, 38);
--highlight-2: rgb(185, 94, 6);
--highlight-3: rgb(207, 0, 0);
--highlight-4: rgb(216, 28, 170);
--highlight-5: rgb(144, 19, 254);
--highlight-6: rgb(5, 98, 185);
--highlight-7: rgb(17, 138, 15);
--highlight-8: rgb(148, 82, 22);
--highlight-9: rgb(102, 102, 102);
--highlight-bg-1: rgba(229, 223, 6, 0.3);
--highlight-bg-2: rgba(255, 185, 87, 0.3);
--highlight-bg-3: rgba(255, 118, 118, 0.3);
--highlight-bg-4: rgba(248, 137, 216, 0.3);
--highlight-bg-5: rgba(190, 165, 255, 0.3);
--highlight-bg-6: rgba(124, 192, 252, 0.3);
--highlight-bg-7: rgba(140, 255, 129, 0.3);
--highlight-bg-8: rgba(221, 170, 123, 0.3);
--highlight-bg-9: rgba(200, 200, 200, 0.3);
/* Colors: Syntax highlighting */
--color-code-token__att: oklch(var(--lch-blue-dark));
--color-code-token__comment: oklch(var(--lch-ink-medium));
--color-code-token__function: oklch(var(--lch-purple-dark));
--color-code-token__operator: oklch(var(--lch-red-dark));
--color-code-token__property: oklch(var(--lch-purple-dark));
--color-code-token__punctuation: oklch(var(--lch-ink-dark));
--color-code-token__selector: oklch(var(--lch-green-dark));
--color-code-token__variable: oklch(var(--lch-red-dark));
/* Colors: Generating gradient */
--color-gradient-1: oklch(var(--lch-violet-lighter));
--color-gradient-2: oklch(var(--lch-pink-lighter));
--color-gradient-3: oklch(var(--lch-purple-lighter));
--color-gradient-4: var(--color-canvas);
}
/* Dark mode - explicit theme choice overrides system preference */
html[data-theme="dark"] {
--lch-canvas: 20% 0.0195 232.58;
--lch-ink-inverted: var(--lch-black);
--lch-ink-darkest: 96.02% 0.0034 260;
--lch-ink-darker: 86% 0.0061 260;
--lch-ink-dark: 73.97% 0.009 260;
--lch-ink-medium: 62% 0.0122 260;
--lch-ink-light: 40% 0.0148 260;
--lch-ink-lighter: 30% 0.0178 260;
--lch-ink-lightest: 25% 0.0204 260;
--lch-uncolor-darkest: 96.09% 0.0076 100;
--lch-uncolor-darker: 86% 0.021 90;
--lch-uncolor-dark: 73.93% 0.041 80;
--lch-uncolor-medium: 62% 0.0552 70;
--lch-uncolor-light: 40% 0.0387 60;
--lch-uncolor-lighter: 30% 0.012 50;
--lch-uncolor-lightest: 25% 0.0017 40;
--lch-red-darkest: 95.85% 0.0218 46;
--lch-red-darker: 86% 0.086 44;
--lch-red-dark: 73.95% 0.139 42;
--lch-red-medium: 62% 0.154 40;
--lch-red-light: 40% 0.088 38;
--lch-red-lighter: 30% 0.032 36;
--lch-red-lightest: 25% 0.011 34;
--lch-yellow-darkest: 96% 0.056 100;
--lch-yellow-darker: 86% 0.103 90;
--lch-yellow-dark: 74.06% 0.136 80;
--lch-yellow-medium: 62.1% 0.146 70;
--lch-yellow-light: 40% 0.0736 60;
--lch-yellow-lighter: 30% 0.026 50;
--lch-yellow-lightest: 25% 0.01 40;
--lch-lime-darkest: 96.04% 0.066 115;
--lch-lime-darker: 86% 0.098 114;
--lch-lime-dark: 73.97% 0.121 113;
--lch-lime-medium: 62% 0.128 112;
--lch-lime-light: 40% 0.0637 111;
--lch-lime-lighter: 30% 0.024 110;
--lch-lime-lightest: 25% 0.012 109;
--lch-green-darkest: 96.12% 0.035 143;
--lch-green-darker: 86% 0.082 144;
--lch-green-dark: 73.99% 0.117 145;
--lch-green-medium: 62% 0.1261 146;
--lch-green-light: 40% 0.065 147;
--lch-green-lighter: 30% 0.03 148;
--lch-green-lightest: 25% 0.018 149;
--lch-aqua-darkest: 96.15% 0.0244 202;
--lch-aqua-darker: 86% 0.06 204;
--lch-aqua-dark: 73.92% 0.095 206;
--lch-aqua-medium: 62% 0.106 208;
--lch-aqua-light: 40% 0.0594 210;
--lch-aqua-lighter: 30% 0.028 212;
--lch-aqua-lightest: 25% 0.017 214;
--lch-blue-darkest: 95.93% 0.0217 252;
--lch-blue-darker: 86% 0.068 254;
--lch-blue-dark: 74% 0.1293 256;
--lch-blue-medium: 62% 0.159 258;
--lch-blue-light: 40% 0.094 260;
--lch-blue-lighter: 30% 0.0452 262;
--lch-blue-lightest: 25% 0.0318 264;
--lch-violet-darkest: 95.97% 0.019 280;
--lch-violet-darker: 86% 0.068 282;
--lch-violet-dark: 74.08% 0.142 284;
--lch-violet-medium: 62% 0.184 286;
--lch-violet-light: 40% 0.108 288;
--lch-violet-lighter: 30% 0.048 290;
--lch-violet-lightest: 25% 0.025 292;
--lch-purple-darkest: 95.99% 0.0217 302;
--lch-purple-darker: 86% 0.068 304;
--lch-purple-dark: 73.98% 0.141 306;
--lch-purple-medium: 62% 0.177 308;
--lch-purple-light: 40% 0.099 310;
--lch-purple-lighter: 30% 0.04 312;
--lch-purple-lightest: 25% 0.017 314;
--lch-pink-darkest: 95.84% 0.0308 336;
--lch-pink-darker: 86% 0.074 338;
--lch-pink-dark: 74.04% 0.1294 340;
--lch-pink-medium: 62% 0.166 342;
--lch-pink-light: 40% 0.085 344;
--lch-pink-lighter: 30% 0.03 346;
--lch-pink-lightest: 25% 0.011 348;
--color-terminal-bg: var(--color-canvas);
--color-terminal-text-light: oklch(var(--lch-green-dark));
--color-golden: oklch(var(--lch-blue-medium));
--color-highlight: oklch(var(--lch-blue-lighter));
--shadow: 0 0 0 1px oklch(var(--lch-black) / 0.42),
0 0.2em 1.6em -0.8em oklch(var(--lch-black) / 0.6),
0 0.4em 2.4em -1em oklch(var(--lch-black) / 0.7),
0 0.4em 0.8em -1.2em oklch(var(--lch-black) / 0.8),
0 0.8em 1.2em -1.6em oklch(var(--lch-black) / 0.9),
0 1.2em 1.6em -2em oklch(var(--lch-black) / 1);
}
/* Fallback to system preference when no explicit theme is set */
@media (prefers-color-scheme: dark) {
html:not([data-theme]) {
--lch-canvas: 20% 0.0195 232.58;
--lch-ink-inverted: var(--lch-black);
--lch-ink-darkest: 96.02% 0.0034 260;
--lch-ink-darker: 86% 0.0061 260;
--lch-ink-dark: 73.97% 0.009 260;
--lch-ink-medium: 62% 0.0122 260;
--lch-ink-light: 40% 0.0148 260;
--lch-ink-lighter: 30% 0.0178 260;
--lch-ink-lightest: 25% 0.0204 260;
--lch-uncolor-darkest: 96.09% 0.0076 100;
--lch-uncolor-darker: 86% 0.021 90;
--lch-uncolor-dark: 73.93% 0.041 80;
--lch-uncolor-medium: 62% 0.0552 70;
--lch-uncolor-light: 40% 0.0387 60;
--lch-uncolor-lighter: 30% 0.012 50;
--lch-uncolor-lightest: 25% 0.0017 40;
--lch-red-darkest: 95.85% 0.0218 46;
--lch-red-darker: 86% 0.086 44;
--lch-red-dark: 73.95% 0.139 42;
--lch-red-medium: 62% 0.154 40;
--lch-red-light: 40% 0.088 38;
--lch-red-lighter: 30% 0.032 36;
--lch-red-lightest: 25% 0.011 34;
--lch-yellow-darkest: 96% 0.056 100;
--lch-yellow-darker: 86% 0.103 90;
--lch-yellow-dark: 74.06% 0.136 80;
--lch-yellow-medium: 62.1% 0.146 70;
--lch-yellow-light: 40% 0.0736 60;
--lch-yellow-lighter: 30% 0.026 50;
--lch-yellow-lightest: 25% 0.01 40;
--lch-lime-darkest: 96.04% 0.066 115;
--lch-lime-darker: 86% 0.098 114;
--lch-lime-dark: 73.97% 0.121 113;
--lch-lime-medium: 62% 0.128 112;
--lch-lime-light: 40% 0.0637 111;
--lch-lime-lighter: 30% 0.024 110;
--lch-lime-lightest: 25% 0.012 109;
--lch-green-darkest: 96.12% 0.035 143;
--lch-green-darker: 86% 0.082 144;
--lch-green-dark: 73.99% 0.117 145;
--lch-green-medium: 62% 0.1261 146;
--lch-green-light: 40% 0.065 147;
--lch-green-lighter: 30% 0.03 148;
--lch-green-lightest: 25% 0.018 149;
--lch-aqua-darkest: 96.15% 0.0244 202;
--lch-aqua-darker: 86% 0.06 204;
--lch-aqua-dark: 73.92% 0.095 206;
--lch-aqua-medium: 62% 0.106 208;
--lch-aqua-light: 40% 0.0594 210;
--lch-aqua-lighter: 30% 0.028 212;
--lch-aqua-lightest: 25% 0.017 214;
--lch-blue-darkest: 95.93% 0.0217 252;
--lch-blue-darker: 86% 0.068 254;
--lch-blue-dark: 74% 0.1293 256;
--lch-blue-medium: 62% 0.159 258;
--lch-blue-light: 40% 0.094 260;
--lch-blue-lighter: 30% 0.0452 262;
--lch-blue-lightest: 25% 0.0318 264;
--lch-violet-darkest: 95.97% 0.019 280;
--lch-violet-darker: 86% 0.068 282;
--lch-violet-dark: 74.08% 0.142 284;
--lch-violet-medium: 62% 0.184 286;
--lch-violet-light: 40% 0.108 288;
--lch-violet-lighter: 30% 0.048 290;
--lch-violet-lightest: 25% 0.025 292;
--lch-purple-darkest: 95.99% 0.0217 302;
--lch-purple-darker: 86% 0.068 304;
--lch-purple-dark: 73.98% 0.141 306;
--lch-purple-medium: 62% 0.177 308;
--lch-purple-light: 40% 0.099 310;
--lch-purple-lighter: 30% 0.04 312;
--lch-purple-lightest: 25% 0.017 314;
--lch-pink-darkest: 95.84% 0.0308 336;
--lch-pink-darker: 86% 0.074 338;
--lch-pink-dark: 74.04% 0.1294 340;
--lch-pink-medium: 62% 0.166 342;
--lch-pink-light: 40% 0.085 344;
--lch-pink-lighter: 30% 0.03 346;
--lch-pink-lightest: 25% 0.011 348;
--color-terminal-bg: var(--color-canvas);
--color-terminal-text-light: oklch(var(--lch-green-dark));
--color-golden: oklch(var(--lch-blue-medium));
--color-highlight: oklch(var(--lch-blue-lighter));
--shadow: 0 0 0 1px oklch(var(--lch-black) / 0.42),
0 .2em 1.6em -0.8em oklch(var(--lch-black) / 0.6),
0 .4em 2.4em -1em oklch(var(--lch-black) / 0.7),
0 .4em .8em -1.2em oklch(var(--lch-black) / 0.8),
0 .8em 1.2em -1.6em oklch(var(--lch-black) / 0.9),
0 1.2em 1.6em -2em oklch(var(--lch-black) / 1);
}
}
================================================
FILE: app/assets/stylesheets/access-tokens.css
================================================
.access-tokens {
border-collapse: collapse;
font-size: var(--text-small);
inline-size: 100%;
margin-block-end: 2lh;
td, th {
border-block-end: 1px solid var(--color-ink-lighter);
text-align: start;
&:first-child {
inline-size: 100%;
}
&:not(:first-child) {
padding-inline-start: var(--inline-space);
}
&:not(:last-child) {
padding-inline-end: var(--inline-space);
}
}
th {
color: var(--color-ink-dark);
font-size: var(--text-x-small);
text-transform: uppercase;
}
td {
padding-block: 8px;
}
}
================================================
FILE: app/assets/stylesheets/android.css
================================================
@layer platform {
[data-platform~=android] {
.hide-on-android {
display: none;
}
/* Filters
/* ------------------------------------------------------------------------ */
.filters {
--text-x-small: 1rem;
}
}
}
================================================
FILE: app/assets/stylesheets/animation.css
================================================
@layer utilities {
.shake {
animation: shake 400ms both;
}
@keyframes appear-then-fade {
0%,100% { opacity: 0; }
5%,60% { opacity: 1; }
}
@keyframes gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
/* Keyframes */
@keyframes react {
0% { transform: scale(0.85); opacity: 0; }
50% { transform: scale(1.15); opacity: 1; }
100% { transform: scale(1); }
}
@keyframes scale-fade-out {
0% { transform: scale(1); opacity: 1; }
100% { transform: scale(0); opacity: 0; }
}
@keyframes shake {
0% { transform: translateX(-2rem); }
25% { transform: translateX(2rem); }
50% { transform: translateX(-1rem); }
75% { transform: translateX(1rem); }
}
@keyframes slide-up {
from { transform: translateY(2rem); }
to { transform: translateY(0); }
}
@keyframes slide-up-fade-in {
from { transform: translateY(2rem); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
@keyframes slide-down {
from { transform: translateY(0); }
to { transform: translateY(2rem); }
}
@keyframes submitting {
0% { -webkit-mask-position: 0% 0%, 50% 0%, 100% 0% }
12.5% { -webkit-mask-position: 0% 50%, 50% 0%, 100% 0% }
25% { -webkit-mask-position: 0% 100%, 50% 50%, 100% 0% }
37.5% { -webkit-mask-position: 0% 100%, 50% 100%, 100% 50% }
50% { -webkit-mask-position: 0% 100%, 50% 100%, 100% 100% }
62.5% { -webkit-mask-position: 0% 50%, 50% 100%, 100% 100% }
75% { -webkit-mask-position: 0% 0%, 50% 50%, 100% 100% }
87.5% { -webkit-mask-position: 0% 0%, 50% 0%, 100% 50% }
100% { -webkit-mask-position: 0% 0%, 50% 0%, 100% 0% }
}
@keyframes success {
0% { background-color: var(--color-border-darker); scale: 0.8; }
33% { background-color: var(--color-border-darker); scale: 1; }
}
@keyframes wiggle {
0% { transform: rotate(0deg); }
20% { transform: rotate(3deg); }
40% { transform: rotate(-3deg); }
60% { transform: rotate(3deg); }
80% { transform: rotate(-3deg); }
100% { transform: rotate(0deg); }
}
@keyframes wobble {
0% { transform: rotate(calc(var(--bubble-rotate) + 30deg)); }
15% { border-radius: 66% 34% 72% 28% / 39% 63% 37% 61%; }
25% { border-radius: 55% 47% 62% 40% / 58% 50% 52% 44%; }
33% { border-radius: 46% 54% 61% 39% / 50% 51% 49% 50%; }
50% { border-radius: 54% 46% 61% 39% / 57% 49% 51% 43%; }
75% { border-radius: 53% 45% 60% 38% / 56% 48% 50% 42%; }
}
@keyframes zoom-fade {
100% { transform: translateY(-1.5em); scale: 2; opacity: 0; }
}
@keyframes blink {
50% {
border-color: transparent;
}
}
}
================================================
FILE: app/assets/stylesheets/attachments.css
================================================
@layer components {
.attachment {
block-size: auto;
display: block;
inline-size: fit-content;
max-inline-size: 100%;
position: relative;
progress {
inline-size: 100%;
margin: auto;
}
}
.attachment__caption {
color: color-mix(in oklch, var(--color-ink) 66%, transparent);
font-size: var(--text-small);
textarea {
--input-border-radius: 0.3em;
--input-border-size: 0;
--input-padding: 0;
background-color: var(--input-background, transparent);
border: none;
color: inherit;
inline-size: 100%;
max-inline-size: 100%;
resize: none;
text-align: center;
&:focus {
--focus-ring-size: 0;
}
@supports (field-sizing: content) {
field-sizing: content;
inline-size: 100%;
}
}
}
.attachment__icon {
aspect-ratio: 4/5;
background-color: color-mix(var(--attachment-icon-color), transparent 90%);
block-size: 2.5lh;
border: 2px solid var(--attachment-icon-color);
border-block-start-width: 1ch;
border-radius: 0.5ch;
box-sizing: border-box;
color: var(--attachment-icon-color);
display: grid;
font-size: var(--text-small);
font-weight: bold;
inline-size: auto;
padding-inline: 0.5ch;
place-content: center;
text-transform: uppercase;
white-space: nowrap;
}
.attachment--preview {
margin-inline: auto;
text-align: center;
img, video {
block-size: auto;
display: block;
margin-inline: auto;
max-inline-size: 100%;
user-select: none;
}
> a {
display: block;
}
.attachment__caption {
column-gap: 0.5ch;
display: flex;
flex-wrap: wrap;
justify-content: center;
margin-block-start: 0.5ch;
}
}
.attachment--file {
--attachment-icon-color: var(--color-ink-medium);
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 1ch;
inline-size: 100%;
margin-inline: 0;
.attachment__caption {
display: grid;
flex: 1;
text-align: start;
}
.attachment__name {
color: var(--color-ink);
font-weight: bold;
}
/* Video attachments don't have an identifiable class, but we need to
* make sure the caption is always below the video */
&:has(video) {
.attachment__caption {
flex: none;
inline-size: 100%;
}
}
}
.attachment--psd,
.attachment--key,
.attachment--sketch,
.attachment--ai,
.attachment--eps,
.attachment--indd,
.attachment--svg,
.attachment--ppt,
.attachment--pptx {
--attachment-icon-color: oklch(var(--lch-red-medium));
}
.attachment--css,
.attachment--crash,
.attachment--php,
.attachment--json,
.attachment--htm,
.attachment--html,
.attachment--rb,
.attachment--erb,
.attachment--ts,
.attachment--js {
--attachment-icon-color: oklch(var(--lch-purple-medium));
}
.attachment--txt,
.attachment--pages,
.attachment--rtf,
.attachment--md,
.attachment--doc,
.attachment--docx {
--attachment-icon-color: oklch(var(--lch-blue-medium));
}
.attachment--csv &,
.attachment--numbers &,
.attachment--xls &,
.attachment--xlsx & {
--attachment-icon-color: oklch(var(--lch-green-medium));
}
.attachment__link {
color: var(--color-link);
text-decoration: underline;
}
/* Custom attachments such as mentions, etc. */
action-text-attachment[content-type^='application/vnd.actiontext'] {
--attachment-bg-color: transparent;
--attachment-image-size: 1em;
--attachment-text-color: currentColor;
align-items: center;
background: var(--attachment-bg-color);
border-radius: 99rem;
box-shadow:
-0.25ch 0 0 var(--attachment-bg-color),
0.5ch 0 0 var(--attachment-bg-color);
color: var(--attachment-text-color);
display: inline-flex;
gap: 0.25ch;
margin: 0;
padding: 0;
position: relative;
vertical-align: bottom;
white-space: normal;
lexxy-editor & {
cursor: pointer;
}
img {
block-size: var(--attachment-image-size);
border-radius: 50%;
inline-size: var(--attachment-image-size);
}
&.node--selected {
--attachment-bg-color: oklch(var(--lch-blue-dark));
--attachment-text-color: var(--color-ink-inverted);
}
}
action-text-attachment[content-type^='application/vnd.actiontext.mention'] {
img {
object-fit: cover;
}
}
}
================================================
FILE: app/assets/stylesheets/autoresize.css
================================================
@layer components {
@supports not (field-sizing: content) {
.autoresize__wrapper {
display: grid !important;
position: relative;
> *, &::after {
grid-area: 1 / 1 / 2 / 2;
}
&::after {
content: attr(data-autoresize-clone-value) " ";
font: inherit;
line-height: inherit;
padding-block: var(--autosize-block-padding, 0);
visibility: hidden;
white-space: pre-wrap;
}
}
.autoresize__textarea {
inset: 0;
overflow: hidden;
position: absolute;
resize: none;
}
}
}
================================================
FILE: app/assets/stylesheets/avatars.css
================================================
@layer components {
.avatar {
--avatar-border-radius: 50%;
--avatar-size-default: 5ch;
--btn-border-size: 0;
aspect-ratio: 1;
block-size: var(--avatar-size, var(--avatar-size-default));
border-radius: var(--avatar-border-radius);
display: grid;
flex-shrink: 0;
inline-size: var(--avatar-size, var(--avatar-size-default));
margin: 0;
place-items: center;
:is(img, .icon) {
aspect-ratio: 1;
block-size: 100%;
border-radius: var(--avatar-border-radius);
grid-area: 1/1;
inline-size: 100%;
max-inline-size: 100%;
object-fit: cover;
}
}
.avatar__form {
display: grid;
grid-template-columns: 1fr auto 1fr;
}
}
================================================
FILE: app/assets/stylesheets/bar.css
================================================
@layer components {
.bar {
--row-gap: 0.2lh;
background-color: var(--color-terminal-bg);
block-size: calc(var(--footer-height) + env(safe-area-inset-bottom));
color: var(--color-terminal-text);
display: flex;
flex-direction: column;
font-size: 0.9em;
inset: auto 0 0 0;
max-block-size: 100%;
padding-block: var(--block-space) calc(var(--block-space) + env(safe-area-inset-bottom));
padding-inline:
calc(var(--tray-size) + calc(var(--inline-space) * 3) + env(safe-area-inset-left))
calc(var(--tray-size) + calc(var(--inline-space) * 3) + env(safe-area-inset-right));
place-content: center;
position: fixed;
view-transition-name: bar;
z-index: var(--z-bar);
html[data-theme="dark"] & {
border-block: 1px solid var(--color-ink-lighter);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
border-block: 1px solid var(--color-ink-lighter);
}
}
&:has(.bar__placeholder[hidden]) {
padding-inline: 1ch;
}
}
::view-transition-group(bar) {
z-index: 99;
}
.bar__input {
transform: translateY(50%);
transition: transform 350ms cubic-bezier(0.25, 1.25, 0.5, 1);
.bar:has(.bar__placeholder[hidden]) & {
transform: translateY(0);
}
}
.bar__modal {
background-color: var(--color-terminal-bg);
block-size: 75dvh;
border-block: 1px solid var(--color-ink-lighter);
inline-size: 100vw;
inset: auto 0 0 0;
max-inline-size: 100vw;
margin-block-end: calc(var(--footer-height) - 0.3rem + env(safe-area-inset-bottom));
position: fixed;
z-index: -1;
&:has(#bar-content[busy]), &:has(#bar-content:not([complete])), &:has([data-search-redirect]) {
display: none;
}
}
.bar__placeholder {
.btn--plain {
color: inherit;
font-size: var(--text-x-small);
font-weight: 600;
opacity: 0.66;
padding-inline: 1ch;
text-transform: uppercase;
white-space: nowrap;
&:hover {
color: oklch(var(--lch-blue-dark));
opacity: 1;
}
}
}
}
================================================
FILE: app/assets/stylesheets/base.css
================================================
@layer base {
html {
font-size: 100%;
@media (min-width: 100ch) {
font-size: 1.1875rem;
}
}
body {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
background: var(--color-canvas);
color: var(--color-ink);
font-family: var(--font-sans);
interpolate-size: allow-keywords;
line-height: 1.375;
max-inline-size: 100vw;
scroll-behavior: auto;
text-rendering: optimizeLegibility;
text-size-adjust: none;
}
a {
text-decoration: none;
&:not([class]) {
color: var(--color-link);
text-decoration: underline;
text-decoration-skip-ink: auto;
}
}
:is(a, button, input, textarea, .switch, .btn) {
transition: 100ms ease-out;
transition-property: background-color, border-color, box-shadow, outline;
touch-action: manipulation;
/* Keyboard navigation */
&:where(:focus-visible) {
border-radius: 0.25ch;
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
/* Default disabled styles */
&:where([disabled]) {
cursor: not-allowed;
opacity: 0.5;
pointer-events: none;
}
}
::selection {
background: var(--color-selected);
html[data-theme="dark"] & {
background-color: var(--color-selected-dark);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
background-color: var(--color-selected-dark);
}
}
}
:where(ul, ol):where([role="list"]) {
margin: 0;
padding: 0;
list-style: none;
}
kbd {
border: 1px solid;
border-radius: 0.3em;
box-shadow: 0 0.1em 0 currentColor;
font-family: var(--font-mono);
font-size: 0.8em;
font-weight: 600;
opacity: 0.7;
padding: 0 0.4em;
text-transform: uppercase;
vertical-align: middle;
white-space: nowrap;
}
video {
max-inline-size: 100%;
}
/* Printing */
@page {
margin: 1in;
}
@media print {
.no-print {
display: none;
}
}
/* Turbo */
turbo-frame,
turbo-cable-stream-source {
display: contents;
}
.turbo-progress-bar {
visibility: hidden;
}
/* Nicer scrollbars on Chrome 29+. This is intended for Windows machines, but */
/* there's not a way to target Windows using CSS, so Chrome on Mac will have */
/* slightly thinner scrollbars than normal. #C1C1C1 is the default color on Macs. */
@media screen and (-webkit-min-device-pixel-ratio:0) and (min-resolution:.001dpcm) {
* {
scrollbar-color: #C1C1C1 transparent;
scrollbar-width: thin;
}
}
}
================================================
FILE: app/assets/stylesheets/blank-slates.css
================================================
/* Styles for the blank slate. To manage when they are shown/hidden, do so in context */
@layer components {
.blank-slate {
border-radius: 0.5ch;
border: 2px dashed var(--color-ink-lighter);
color: var(--color-ink-dark);
font-weight: 500;
margin-block-start: 2dvh;
padding: 1.5ch 2ch;
rotate: -3deg;
}
.blank-slate--drag {
background-color: color-mix(in srgb, transparent, var(--card-color) 5%);
border-color: color-mix(in srgb, transparent, var(--card-color) 10%);
color: color-mix(in srgb, transparent, var(--card-color) 75%);
margin-block-start: 0;
rotate: 0deg;
}
}
================================================
FILE: app/assets/stylesheets/bubble.css
================================================
@layer components {
.bubble {
--bubble-color: var(--card-color, oklch(var(--lch-blue-medium)));
--bubble-number-max: 26px;
--bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%;
--bubble-rotate: 0deg;
--bubble-size-default: 4rem;
block-size: var(--bubble-size, var(--bubble-size-default));
color: var(--card-content-color);
container-type: inline-size;
font-size: 1.4rem;
font-weight: bold;
inline-size: var(--bubble-size, var(--bubble-size-default));
inset-block-start: 20%;
padding: 0.5cqi;
position: absolute;
z-index: 1;
&:before {
background: radial-gradient(
color-mix(in srgb, var(--bubble-color) 8%, var(--color-canvas)) 50%,
color-mix(in srgb, var(--bubble-color) 48%, var(--color-canvas)) 100%
);
border-radius: var(--bubble-shape);
content: "";
inset: 0;
position: absolute;
transform: rotate(var(--bubble-rotate));
z-index: -1;
}
@media (any-hover: hover) {
&:hover:before {
animation: wobble 1200ms;
}
}
svg {
display: block;
letter-spacing: 0.2ch;
text-transform: uppercase;
}
}
.bubble__number {
display: grid;
font-size: clamp(10px, 50cqi, var(--bubble-number-max)); /* FF bug: https://app.fizzy.do/5986089/boards/2/cards/1373 */
font-weight: 900;
inset: 0;
place-content: center;
position: absolute;
text-align: center;
}
}
================================================
FILE: app/assets/stylesheets/buttons.css
================================================
@layer components {
.btn {
--icon-size: var(--btn-icon-size, 1.3em);
--btn-border-radius: 99rem;
--btn-hover-brightness: 0.9;
align-items: center;
background-color: var(--btn-background, var(--color-canvas));
border-radius: var(--btn-border-radius);
border: var(--btn-border-size, 1px) solid var(--btn-border-color, var(--color-ink-light));
color: var(--btn-color, var(--color-ink));
cursor: pointer;
display: inline-flex;
font-size: 1em;
font-weight: var(--btn-font-weight, 600);
gap: var(--btn-gap, 0.5em);
justify-content: center;
padding: var(--btn-padding, 0.5em 1.1em);
pointer-events: auto;
position: relative;
text-decoration: none;
transition: 100ms ease-out;
transition-property: background-color, border, box-shadow, color, opacity, scale;
@media (any-hover: hover) {
&:hover {
filter: brightness(var(--btn-hover-brightness));
}
}
html[data-theme="dark"] & {
--btn-hover-brightness: 1.25;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
--btn-hover-brightness: 1.25;
}
}
&[disabled],
&:has([disabled]),
[disabled] &[type=submit],
&[type=submit]:disabled {
cursor: not-allowed;
opacity: 0.3;
pointer-events: none;
}
form[aria-busy] &:disabled {
position: relative;
> * {
visibility: hidden;
}
&::after {
--mask: no-repeat radial-gradient(#000 68%,#0000 71%);
--size: 1.25em;
-webkit-mask: var(--mask), var(--mask), var(--mask);
-webkit-mask-size: 28% 45%;
animation: submitting 1s infinite linear;
aspect-ratio: 8/5;
background: currentColor;
content: "";
inline-size: var(--size);
inset: 50%;
margin-block: calc((var(--size) / 3) * -1);
margin-inline: calc((var(--size) / 2) * -1);
position: absolute;
}
}
}
/* Variants
/* ------------------------------------------------------------------------ */
.btn--plain {
--btn-background: transparent;
--btn-border-radius: 0;
--btn-border-size: 0;
--btn-color: inherit;
--btn-icon-size: 100%;
--btn-padding: 0;
}
.btn--link {
--btn-background: var(--color-link);
--btn-border-color: var(--color-canvas);
--btn-color: var(--color-ink-inverted);
--focus-ring-color: var(--color-link);
}
.btn--circle,
.btn[aria-label]:where(:has(.icon)),
.btn:where(:has(.for-screen-reader):has(.icon)) {
--btn-padding: 0;
--icon-size: 75%;
aspect-ratio: 1;
block-size: var(--btn-size);
display: grid;
inline-size: var(--btn-size);
justify-content: normal; /* FF fix */
place-items: center;
> * {
grid-area: 1/1;
}
}
/* Make a normal button circular on mobile */
@media (max-width: 639px) {
.btn--circle-mobile {
--btn-size: 3em;
--btn-padding: 0;
--icon-size: 75%;
aspect-ratio: 1;
inline-size: var(--btn-size);
kbd,
span:last-of-type:not(.icon) {
display: none;
}
}
}
@media (min-width: 640px) {
.btn .icon--mobile-only {
display: none !important;
}
}
.btn--negative {
--btn-background: var(--color-negative);
--btn-border-color: var(--color-negative);
--btn-color: var(--color-ink-inverted);
--focus-ring-color: var(--color-negative);
}
.btn--positive {
--btn-background: var(--color-positive);
--btn-border-color: var(--color-canvas);
--btn-color: var(--color-ink-inverted);
--focus-ring-color: var(--color-positive);
}
.btn--success {
--success-timing-function: cubic-bezier(0.25, 1.25, 0.5, 1);
animation: success 1s var(--success-timing-function);
.icon {
animation: zoom-fade 500ms var(--success-timing-function);
}
}
/* Fake button used to help space things out */
.btn--placeholder {
pointer-events: none;
visibility: hidden;
}
.btn--remove {
--btn-icon-size: 0.7em;
}
.btn--reversed {
--btn-background: var(--color-ink);
--btn-border-color: var(--color-canvas);
--btn-color: var(--color-canvas);
--focus-ring-color: var(--color-ink);
}
/* Toggleable buttons
/* ------------------------------------------------------------------------ */
.btn {
&:has(input[type=radio], input[type=checkbox]) {
position: relative;
:is(input[type=radio], input[type=checkbox]) {
appearance: none;
border-radius: var(--btn-border-radius);
cursor: pointer;
display: flex;
inset: 0;
margin: 0;
padding: 0;
position: absolute;
&:focus-visible {
outline: none;
}
}
.checked {
display: none;
}
}
&:has(input:checked) {
--btn-background: var(--color-ink);
--btn-border-color: var(--color-ink);
--btn-color: var(--color-ink-inverted);
--focus-ring-color: var(--color-ink);
.checked {
display: block;
}
}
&:has(input:focus-visible) {
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
}
.btn--back {
--btn-border-size: 0;
@media (max-width: 639px) {
strong, kbd {
display: none;
}
}
@media (min-width: 640px) {
font-size: var(--text-medium);
.icon--arrow-left {
display: none;
}
}
}
/* Button groups
/* ------------------------------------------------------------------------ */
.btn__group {
.btn {
--btn-border-radius: 0;
--radius: 0.3em;
flex: 1 0 33%;
inline-size: 100%;
justify-content: center;
white-space: nowrap;
}
form {
flex: 1 1 0%;
}
:first-of-type .btn {
border-end-start-radius: var(--radius);
border-inline-end: 0;
border-start-start-radius: var(--radius);
padding-inline-end: 0.8em;
}
:last-of-type .btn {
border-end-end-radius: var(--radius);
border-inline-start: 0;
border-start-end-radius: var(--radius);
padding-inline-start: 0.8em;
}
span {
inline-size: 100%;
}
}
/* Button utilities
/* ------------------------------------------------------------------------ */
:is([data-platform~=mobile], [data-platform~=native]) {
.btn--ensure-tap-target-size {
--tap-target-z-index: 1;
--tap-target-min-size: 44px;
z-index: var(--tap-target-z-index);
&::before {
content: "";
display: block;
block-size: var(--tap-target-min-size);
inline-size: var(--tap-target-min-size);
inset: calc(50% - var(--tap-target-min-size) / 2) auto auto calc(50% - var(--tap-target-min-size) / 2);
opacity: 0;
position: absolute;
}
}
}
}
================================================
FILE: app/assets/stylesheets/card-columns.css
================================================
@layer components {
/* Layout adjustments for contained scrolling
/* ------------------------------------------------------------------------ */
/* Scroll columns individually on mobile */
@media (max-width: 639px) {
body.contained-scrolling {
block-size: 100dvh;
grid-template-rows: 1fr var(--footer-height);
#global-container {
display: grid;
grid-template-rows: auto 1fr;
overflow: hidden;
}
#main {
display: grid;
grid-template-rows: auto auto 1fr;
overflow: auto;
padding: 0;
}
/* Adapt the grid to public views (no filters or watchers sections) */
&.public #main {
grid-template-rows: 1fr;
}
}
}
/* Column container
/* ------------------------------------------------------------------------ */
#main:has(.card-columns) {
--main-padding: 0;
}
.card-columns {
--bubble-size: 3.5rem;
--cards-gap: min(1.2cqi, 1.7rem);
--column-gap: 8px;
--column-padding: calc(var(--column-gap) * 2);
--column-transition-duration: 300ms;
--column-width-collapsed: 40px;
--column-width-expanded: 450px;
--progress-increment: var(--progress-max-height) / var(--progress-max-cards);
--progress-max-cards: 15; /* should match first geared pagination page size */
--progress-max-height: 50dvh;
container-type: inline-size;
display: grid;
gap: var(--column-gap);
grid-template-columns: 1fr auto 1fr;
inline-size: 100%;
margin-inline: auto;
max-inline-size: var(--main-width);
outline: none;
overflow-x: auto;
overflow-y: hidden;
position: relative;
/* When it has something expanded */
&:has(.card-columns__left .is-expanded, .card-columns__right .is-expanded) {
grid-template-columns: auto auto auto;
@media (min-width: 640px) {
grid-template-columns: auto var(--column-width-expanded) auto;
}
}
&:has(.cards) {
block-size: 100%;
min-block-size: 20lh;
}
@media (max-width: 639px) {
--column-width-expanded: calc(100vw - var(--column-gap) * 4);
scroll-snap-type: inline mandatory;
&:not(:has(.is-expanded)) {
grid-template-columns: auto var(--column-width-collapsed) auto;
}
}
@media (min-width: 640px) {
padding-block-end: var(--column-width-collapsed);
}
}
.card-columns__left,
.card-columns__right {
align-items: stretch;
display: flex;
gap: var(--column-gap);
position: relative;
@media (max-width: 639px) {
min-block-size: 0;
}
}
.card-columns__left {
justify-content: end;
margin-inline-start: auto;
padding-inline-start: var(--column-gap);
@media (max-width: 639px) {
padding-inline-start: calc(var(--column-gap) * 2);
}
}
.card-columns__right {
justify-content: start;
padding-inline-end: var(--column-gap);
margin-inline-end: auto;
}
/* Column
/* ------------------------------------------------------------------------ */
.cards {
--column-color: color-mix(in srgb, var(--card-color) 15%, var(--color-canvas));
inline-size: var(--column-width-expanded);
outline: none;
position: relative;
scroll-snap-align: center;
&.is-expanded {
@media (max-width: 639px) {
overflow: hidden;
}
}
&.is-collapsed {
inline-size: var(--column-width-collapsed);
.pagination-link.pagination-link--active-when-observed,
.card {
display: none;
}
}
&.drag-and-drop__hover-container {
--dnd-bg-color: transparent;
--dnd-border-color: transparent;
&.is-off-screen {
&:after {
content: attr(data-column-name);
font-size: var(--text-x-small);
font-weight: 500;
line-height: var(--column-width-collapsed);
padding-inline: 1ch;
position: fixed;
text-transform: uppercase;
top: 0;
translate: -50%;
}
&.is-collapsed {
&:after {
writing-mode: vertical-rl;
}
}
&:not(.is-collapsed) {
&:after {
background-color: var(--column-color);
inline-size: calc(var(--column-width-expanded) - 4px); /* make room for the dnd border */
}
}
}
}
@media (any-hover: hover) {
.card:has(.card__background img:not([src=""])):hover .card__background img:not([src=""]) {
filter: blur(3px) brightness(1.2);
opacity: 0.2;
}
}
}
.cards__transition-container {
block-size: 100%;
border-radius: calc(var(--column-width-collapsed) / 2);
margin-block-start: 0.5ch; /* Allow a little room for the mini bubble */
transition: translate var(--column-transition-duration) var(--ease-out-overshoot-subtle);
@media (min-width: 640px) {
.is-expanded & {
translate: 0; /* Animate back from collapsed state */
}
.is-collapsed & {
margin-block-start: 0;
translate: 0 var(--column-width-collapsed);
}
}
.drag-and-drop__hover-container & {
--dnd-bg-color: var(--column-color);
--dnd-border-color: var(--card-color);
background-color: var(--dnd-bg-color);
outline: 2px dashed var(--dnd-border-color);
outline-offset: -2px;
transition: background-color 200ms;
z-index: 1;
}
.no-transitions & {
transition: none;
}
/* Use flex so the __list container can take up the remaining space for scrolling */
@media (max-width: 639px) {
.is-expanded & {
display: flex;
flex-direction: column;
}
}
}
/* The wrapper around the cards used to clip overflow while transitioning.
* Also, don't resize cards while transitioning to avoid reflow. */
.cards__list {
display: flex;
flex-direction: column;
gap: var(--cards-gap);
overflow-x: hidden;
overflow-y: auto;
.is-expanded & {
padding: var(--column-padding) var(--column-padding) calc(var(--column-padding) + var(--custom-safe-inset-bottom));
/* Use the rest of the column height for scrolling */
@media (max-width: 639px) {
flex: 1;
padding-inline: calc(var(--column-padding) / 4);
}
}
[aria-selected] & .card[aria-selected] {
outline: var(--focus-ring-size) solid var(--color-selected-dark);
outline-offset: var(--focus-ring-offset);
html[data-theme="dark"] & {
outline-color: oklch(var(--lch-blue-medium));
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
outline-color: oklch(var(--lch-blue-medium));
}
}
}
&:has(.card) {
.blank-slate {
display: none;
}
}
/* Use the default blank-slate on small viewports since drag-and-drop isn't available */
[data-controller~="drag-and-drop"] & {
@media (max-width: 639px) {
.blank-slate--drag {
display: none;
}
}
@media (min-width: 640px) {
.blank-slate--default {
display: none;
}
}
}
}
.cards__new-column {
position: relative;
@media (max-width: 639px) {
inset-inline-end: 0;
position: absolute;
translate: 100%;
z-index: 2;
}
@media (min-width: 640px) {
margin-block-start: var(--column-width-collapsed);
}
}
/* Cards grid; used when filtering
/* -------------------------------------------------------------------------- */
.cards--grid {
--cards-gap: 1rem;
--card-grid-columns: 1;
container-type: inline-size;
inline-size: 100%;
margin-inline: auto;
max-inline-size: var(--main-width);
@media (min-width: 640px) {
--card-grid-columns: 2;
}
@media (min-width: 960px) {
--card-grid-columns: 3;
}
.cards__list {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: var(--cards-gap);
justify-content: center;
padding: 1ch;
}
.card {
inline-size: calc((100% - var(--cards-gap) * (var(--card-grid-columns) - 1) ) / var(--card-grid-columns));
}
.card__header .card__column-name--current {
--btn-padding: 0.1em 0.5em;
background: none;
border: 1px solid currentColor;
color: var(--card-color);
display: inline-flex;
flex: 0 1 auto;
inline-size: fit-content;
margin: 0 0 0 auto;
}
.blank-slate--drag {
display: none;
}
}
/* Column Elements
/* ------------------------------------------------------------------------ */
.cards__header {
.cards.is-collapsed & {
block-size: 100%;
}
.cards.is-expanded & {
display: grid;
grid-template-areas: "menu expander maximize";
grid-template-columns: var(--column-width-collapsed) 1fr var(--column-width-collapsed);
padding-inline: var(--column-padding);
}
}
.cards__menu .btn--circle,
.cards__maximize-button {
--btn-background: transparent;
block-size: var(--column-width-collapsed);
inline-size: var(--column-width-collapsed);
opacity: 0;
outline-offset: -2px;
.cards:hover &,
&:focus-visible {
opacity: 1;
}
.cards.is-collapsed & {
display: none;
}
}
.cards__menu {
position: relative;
z-index: var(--z-popup);
}
.cards__maximize-button {
grid-area: maximize;
}
.cards__expander {
--gradient-direction: to bottom;
align-items: center;
border-radius: 99rem;
cursor: pointer;
display: flex;
flex-direction: row-reverse;
font-size: var(--text-x-small);
font-weight: 600;
gap: 0.5ch;
grid-area: expander;
justify-content: center;
outline: none;
outline-offset: -2px;
position: relative;
text-transform: uppercase;
&[disabled] {
opacity: 1;
}
@media (any-hover: hover) {
.is-collapsed:hover {
filter: brightness(0.9);
}
}
/* Progress */
&:after {
background: linear-gradient(var(--gradient-direction), var(--card-color), var(--column-color) 80%);
block-size: var(--column-width-collapsed);
border-radius: 99rem;
content: "";
inset: 0 0 auto;
margin-inline: auto;
max-block-size: var(--progress-max-height);
min-block-size: var(--column-width-collapsed);
opacity: 0;
position: absolute;
transition:
block-size 500ms var(--ease-out-overshoot),
inline-size var(--column-transition-duration) ease-out,
opacity var(--column-transition-duration) ease-out;
z-index: -1;
}
.no-transitions &:after {
transition: none;
}
.cards.is-collapsed & {
block-size: 100%;
flex-direction: column;
inline-size: var(--column-width-collapsed);
justify-content: start;
letter-spacing: 0.05em;
/* Guitar string */
&:before {
background-color: var(--column-color);
block-size: 100%;
content: "";
inline-size: 1px;
inset-block: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment)) 0;
position: absolute;
z-index: -2;
}
&:after {
block-size: calc(var(--column-width-collapsed) + var(--card-count) * var(--progress-increment));
max-block-size: none;
opacity: 1;
inline-size: var(--column-width-collapsed);
}
}
.cards.is-expanded & {
inline-size: 100%;
justify-content: center;
}
}
.cards__expander-count {
line-height: var(--column-width-collapsed);
inline-size: var(--column-width-collapsed);
.cards.is-expanded & {
display: none;
}
}
.cards__expander-title {
font-weight: inherit;
font-size: inherit;
line-height: var(--column-width-collapsed);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
.cards.is-collapsed & {
max-inline-size: 50vh;
writing-mode: vertical-rl;
}
.cards.is-expanded & {
align-items: center;
display: flex;
gap: 0.25ch;
max-inline-size: calc(100% - var(--column-width-collapsed) * 2);
}
.icon--collapse {
--icon-size: 1.15em;
opacity: 0.66;
transition: 150ms ease-out;
transition-property: opacity, scale;
@media (min-width: 640px) {
opacity: 0;
scale: 1.5;
}
.cards.is-collapsed & {
display: none;
}
.cards.is-expanded .cards__expander:hover & {
opacity: 0.66;
scale: 1;
}
}
}
/* Override card styles within columns
/* Adding .board-tools here since it sits outside the cards container on mobile */
/* ------------------------------------------------------------------------ */
.cards .card,
.board-tools {
--block-space: 1em;
--block-space-half: 0.5em;
--card-padding-inline: 1em;
--text-xx-large: 1.6em;
--text-x-small: 1em;
/* Set lower limit for font size */
font-size: clamp(0.6rem, 0.85cqi, 100px);
.card__counts {
--gap: 0.5ch;
align-items: flex-end;
display: flex;
flex-shrink: 0;
gap: calc(2 * var(--gap));
margin-inline: auto calc(var(--card-padding-inline) * -0.5);
padding-inline-start: var(--gap);
}
.card__boosts,
.card__comments {
--icon-size: 1.6em;
align-items: center;
display: flex;
flex-shrink: 0;
font-weight: 600;
gap: var(--gap);
img {
block-size: var(--icon-size);
inline-size: var(--icon-size);
}
.icon--comment {
color: var(--card-color);
}
}
.card__steps {
--column-gap: 0.8ch;
display: flex;
}
.card__tags {
gap: calc(var(--card-header-space) / 2);
}
.card__title {
pointer-events: none;
}
.card__link {
z-index: 1;
}
.card__stages,
.card__hide-on-index {
display: none;
}
.card__body {
padding-block: calc(var(--card-padding-block) * 0.75) var(--card-padding-block);
}
.card__meta {
font-weight: 600;
strong,
.local-time-value {
font-weight: inherit;
}
@media (max-width: 639px) {
inline-size: auto;
}
}
&:has(.card__background img:not([src=""])) {
.card__content,
.card__meta,
.card__boosts,
.card__comments,
.card__column-name:not(.card__column-name--current) {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
@media (any-hover: hover) {
&:hover {
.card__content,
.card__footer,
.card__boosts,
.card__comments,
.card__column-name:not(.card__column-name--current) {
opacity: 1;
}
.card__background img {
filter: blur(3px) brightness(1.2);
opacity: 0.2;
}
}
}
}
.bubble {
inset-inline-start: 100%;
translate: -90% -40%;
}
}
/* Considering
/* ------------------------------------------------------------------------ */
.cards--maybe {
--card-color: oklch(var(--lch-blue-medium));
position: relative;
.card {
--avatar-size: 2.75em;
--text-small: 1.1em;
background-color: var(--color-canvas);
line-height: 1.2;
z-index: 2;
@media (min-width: 640px) {
--text-xx-large: 1.6em;
}
}
.card__board {
background-color: transparent;
color: var(--card-content-color);
}
.card__header {
color: var(--color-ink);
padding-block-start: calc(var(--card-padding-block) / 2);
}
.card__tags {
color: inherit;
}
.card__body {
padding-block: 0 var(--card-padding-block);
}
.card__people-label {
display: none;
}
.card__title {
min-block-size: 0;
}
}
/* Board tools
/* -------------------------------------------------------------------------- */
.board-tools.card {
--border-color: var(--color-selected-dark);
--border-size: 1px;
--card-padding-block: var(--block-space);
border: 1px solid var(--border-color);
inline-size: auto;
text-align: center;
@media (max-width: 639px) {
/* On mobile, hide the tool card inside the Maybe column */
.cards & {
display: none;
}
#cards_container > & {
margin: 0 3ch 1ch;
}
}
@media (min-width: 640px) {
/* On desktop, hide the tool card above the columns */
#cards_container > & {
display: none;
}
}
@media (min-width: 800px) {
margin: var(--column-padding) var(--column-padding) 0;
}
&:has(dialog[open]) {
z-index: 5;
}
.divider {
--divider-color: oklch(var(--lch-blue-light));
}
.btn--link {
font-size: 1.2em;
}
.btn:not(.btn--link, .btn--circle) {
border: 0;
color: var(--color-link);
}
footer {
font-size: var(--text-x-small);
margin-block: 1ch calc(var(--block-space-half) * -1);
}
.overflow-count {
font-size: 1.2em;
font-weight: 500;
padding: 0.5em 0.3em;
}
}
.board-tools__watching {
--btn-size: 32px;
--gap: 0.5ch;
display: flex;
gap: var(--gap);
inline-size: 100%;
margin-block: var(--block-space-half);
place-content: center;
position: relative;
}
.board-tools__watching-dialog {
--panel-padding: 2ch;
--panel-size: 100%;
flex-wrap: wrap;
gap: var(--gap);
inset-block-start: 0;
justify-content: center;
position: absolute;
z-index: 1;
&[open] {
display: flex;
}
}
/* On Deck (Not Now)
/* ------------------------------------------------------------------------ */
.cards--closed,
.cards--on-deck {
--card-color: var(--color-ink-light) !important;
.card,
.blank-slate {
--card-color: var(--color-card-complete) !important;
}
.bubble {
display: none !important;
}
}
/* Doing
/* -------------------------------------------------------------------------- */
/* Surface a mini bubble if there are cards with bubbles inside */
.cards--maybe:has(.bubble:not([hidden])) .cards__expander-title,
.cards--maybe.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container,
.cards--doing.is-collapsed:has(.bubble:not([hidden])) .cards__transition-container {
--bubble-color: var(--card-color, oklch(var(--lch-blue-medium)));
--bubble-opacity: 75%;
--bubble-shape: 54% 46% 61% 39% / 57% 49% 51% 43%;
&:before {
background: radial-gradient(
color-mix(in srgb, var(--bubble-color) calc(var(--bubble-opacity) / 5), var(--color-canvas)) 50%,
color-mix(in srgb, var(--bubble-color) var(--bubble-opacity), var(--color-canvas)) 100%
);
block-size: 1em;
border-radius: var(--bubble-shape);
content: "";
inline-size: 1em;
inset: 0 0 auto auto;
position: absolute;
translate: 20% -20%;
z-index: 1;
@media (max-width: 639px) {
translate: 20% 0%;
}
}
/* Maybe column: position bubble relative to the title, not the container */
.cards--maybe.is-expanded & {
overflow: visible;
position: relative;
&:before {
inset-block-start: 50%;
inset-inline-start: 0;
translate: -125% -75%;
z-index: -1;
}
}
@media (max-width: 639px) {
&.cards__expander-title:before {
display: none;
}
}
html[data-theme="dark"] & {
--bubble-opacity: 100%;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
--bubble-opacity: 100%;
}
}
}
/* Card column indicators
/* -------------------------------------------------------------------------- */
.card__column-name {
--btn-background: transparent;
--btn-padding: 0.2em 0.5em;
--btn-border-size: 0;
--btn-border-radius: 0.2em;
color: inherit;
inline-size: 100%;
justify-content: flex-start;
text-transform: uppercase;
@media (hover: hover) {
&:not(.card__column-name--current):hover {
--btn-background: color(from var(--column-color) srgb r g b / 0.15);
color: var(--column-color);
}
}
}
.card__column-name--current {
--btn-background: var(--card-color);
color: var(--color-ink-inverted);
opacity: 1 !important;
@media (hover: hover) {
&:hover {
--btn-background: var(--card-color);
}
}
}
}
================================================
FILE: app/assets/stylesheets/card-perma.css
================================================
/* Card container for the perma. Tools and actions and whatnot */
@layer components {
.card-perma {
--actions-block-inset: 1.5rem;
--actions-inline-inset: 4rem;
--color-container: color-mix(in srgb, var(--card-color) 33%, var(--color-canvas));
--half-btn-height: 1.25rem;
--padding-inline: calc(var(--block-space-double) + var(--block-space));
--padding-block: calc(var(--block-space-double) + var(--block-space-half));
align-items: start;
column-gap: var(--inline-space);
display: grid;
grid-template-areas:
"notch-top notch-top notch-top"
"actions-left card actions-right"
"notch-bottom notch-bottom notch-bottom"
"closure-message closure-message closure-message";
grid-template-columns: 48px minmax(0, 1120px) 48px;
inline-size: fit-content;
margin-block-start: var(--block-space);
max-inline-size: 100%;
margin-inline: auto;
position: relative;
&:has(dialog[open]) {
z-index: 3;
}
&:has(.card-perma__star-input:checked) {
.card {
outline: 4px solid var(--color-negative);
}
}
@media (max-width: 799px) {
--half-btn-height: 1.25rem;
--padding-inline: 1.5ch;
column-gap: 0;
grid-template-areas:
"notch-top notch-top notch-top"
"card card card"
"actions-left notch-bottom actions-right"
"closure-message closure-message closure-message";
grid-template-columns: 1fr auto 1fr;
inline-size: calc(100% + 2 * var(--padding-inline));
margin-inline: calc(-1 * var(--padding-inline));
max-inline-size: none;
position: relative;
}
.card {
--card-aspect-ratio: 2 / 0.95;
--lexxy-bg-color: var(--card-bg-color);
border: none;
}
.card__background {
filter: brightness(1.2) contrast(0.8);
opacity: 0.2;
}
.card__header {
@media (max-width: 639px) {
flex-wrap: wrap;
gap: var(--card-header-space) unset;
}
}
.card__tags {
@media (max-width: 639px) {
padding: 0.25lh;
}
}
.card__tags-list {
@media (min-width: 640px) {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
.card__body {
position: relative;
@media (max-width: 639px) {
flex-direction: column;
padding-block: var(--card-padding-block) calc(var(--card-padding-block) * 1.5);
position: static;
}
}
.card__content {
display: flex;
flex-direction: column;
gap: 1ch;
}
.card__title {
font-size: clamp(var(--text-medium), 6vw, var(--text-x-large));
margin-block-end: 0.5ch;
/* With tight line spacing, Windows will cover over adjacent lines of text
* when input text is selected. Here, we're setting the selection color to a
* transparent value so the overlapping text lines are at least visible. */
::selection {
background: oklch(var(--lch-blue-light) / 0.5);
}
&:has(textarea) {
@media (min-width: 640px) {
margin-block-end: 0;
}
@supports not (field-sizing: content) {
text-wrap: unset; /* Safari is annoying if you have text-wrap: balance in textareas */
}
}
@media (max-width: 639px) {
margin-block-end: 0.75ch;
}
}
.card__description {
@media (max-width: 639px) {
margin-block-end: 1ch;
}
}
.card__meta,
.card__stages {
@media (min-width: 640px) {
font-size: var(--text-small);
}
}
.card__meta {
grid-area: meta;
margin-inline-end: auto;
@media (max-width: 639px) {
--meta-spacer-block: 0.75ch;
min-inline-size: 0;
gap: calc(var(--meta-spacer-block) / 2);
display: flex;
flex-wrap: wrap;
.card__meta-text {
border: 0;
padding: 0;
}
.card__meta-avatars--author {
--btn-size: 1.5em;
display: initial;
margin-inline-end: unset;
order: 3;
}
.card__meta-text--added {
inline-size: 100%;
order: 1;
}
.card__meta-text--author {
order: 2;
}
.card__meta-text--updated {
border-block-start: var(--card-border);
inline-size: 100%;
margin-block-start: calc(var(--meta-spacer-block) * 0.5);
order: 4;
padding-block-start: var(--meta-spacer-block);
}
.card__meta-text--assignees {
margin-block-start: calc(var(--meta-spacer-block) * 3);
order: 6;
white-space: unset !important;
}
.card__meta-avatars--assignees {
margin-inline: 0 var(--meta-spacer-inline);
margin-block-start: calc(var(--meta-spacer-block) * 3);
order: 5;
.avatar {
display: grid;
}
}
&:has(.card__meta-avatars--assignees .avatar) {
.card__meta-text--assignees {
order: 5;
}
.card__meta-avatars--assignees {
margin-block-start: var(--meta-spacer-block);
order: 6;
}
}
}
}
&:has(.card__closed) .card__meta {
@media (max-width: 639px) {
.card__meta-avatars--assignees {
display: none;
}
}
}
.card__stages {
max-inline-size: 32ch;
@media (max-width: 639px) {
border: 1px solid var(--card-color);
border-radius: calc(0.2em + 3px);
flex-direction: row;
gap: 0;
overflow: auto;
max-inline-size: 100%;
padding: 3px;
position: relative;
white-space: nowrap;
& > form {
flex-grow: 1;
max-inline-size: 25ch;
min-block-size: 2.5em;
}
& > form:not(:has(.card__column-name--current)) + form:not(:has(.card__column-name--current)) {
box-shadow: -1px 0 0 0 var(--color-container);
}
}
}
.card__column-name {
@media (max-width: 639px) {
justify-content: center;
}
}
.card__closed {
@media (max-width: 639px) {
inset: auto 0 3rem auto;
scale: 75%;
}
}
.card__footer {
--btn-size: 2.5rem;
display: flex;
gap: 0.5ch;
inline-size: 100%;
text-align: start;
/* Switch to grid layout so that the bg zoom button can stay next to the
* meta element, and the reactions can sit below */
&:has(.reaction) {
display: grid;
grid-template-columns: 1fr auto;
grid-template-areas:
"meta bg-zoom"
"reactions reactions";
}
@media (max-width: 639px) {
display: grid;
font-size: var(--text-x-small);
gap: 1ch 0;
grid-template-columns: 1fr auto;
grid-template-areas:
"meta bg-zoom"
"meta reactions";
&:not(:has(.reaction)),
&:has(.card__background) {
column-gap: 2ch;
}
}
}
.reactions {
--reaction-size: var(--btn-size);
align-self: flex-end;
display: flex;
gap: 0.5ch;
grid-area: reactions;
margin-inline-start: auto;
&:has(.reaction) {
--padding: calc(var(--card-padding-block) / 2);
--reaction-size: 1.6875rem;
margin-block: var(--padding) calc(-1 * var(--padding));
padding-block-start: var(--padding);
position: relative;
&:before {
border-block-start: 1px dashed color-mix(in srgb, transparent, var(--card-color) 33%);
content: "";
inset: 0 calc(-1 * var(--card-padding-inline)) auto;
position: absolute;
}
@media (any-hover: none) {
--reaction-size: 2.25rem;
}
}
&:not(:has(.reaction)) {
margin: 0;
.reactions__trigger {
--btn-border-color: var(--color-ink-light);
}
}
}
.reaction__popup.popup {
inline-size: max-content;
}
.card__zoom-bg-btn {
grid-area: bg-zoom;
}
.bubble {
--bubble-number-max: 42px;
--bubble-size: 6rem;
inset: calc(var(--bubble-size) / -4) calc(var(--bubble-size) / 1.5) auto auto;
translate: 0 0;
@media (max-width: 799px) {
--bubble-size: 4.5rem;
inset: calc(var(--bubble-size) / 1.5) 0 auto auto;
}
}
}
/* Child items
/* ------------------------------------------------------------------------ */
.card-perma__bg {
background-color: var(--color-container);
border-radius: 0.2em;
grid-area: card;
padding: clamp(2rem, 4vw, var(--padding-block));
@media (max-width: 639px) {
padding: clamp(0.25rem, 2vw, var(--padding-block));
padding-block-end: clamp(2.5rem, 4vw, var(--padding-block));
}
@media (min-width: 640px) and (max-width: 799px) {
padding-inline: var(--padding-inline);
}
}
.card-perma__actions {
display: grid;
gap: var(--block-space-half);
&:has([open]) {
position: relative;
z-index: 1;
}
&:has([data-controller~="tooltip"]:hover) {
z-index: var(--z-tooltip);
}
}
.card-perma__actions--left { grid-area: actions-left; }
.card-perma__actions--right { grid-area: actions-right; }
@media (max-width: 799px) {
.card-perma__actions {
display: flex;
padding-inline: var(--padding-inline);
translate: 0 -50%;
}
.card-perma__actions--right {
inset-inline-end: 0;
justify-content: flex-end;
}
}
.card-perma__image-btn {
&:has(input[type="file"]:focus),
&:has(input[type="file"]:focus-visible) {
outline: var(--focus-ring) !important;
outline-offset: var(--focus-ring-offset);
}
input[type="file"] {
outline: none;
}
}
.card__banner {
align-items: center;
background-color: var(--color-highlight);
border-radius: 2em;
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
display: inline-flex;
inline-size: auto;
gap: var(--inline-space-half);
grid-area: notch-top;
justify-content: center;
margin-block-start: -4ch;
margin-inline: auto;
max-inline-size: 36ch;
padding: var(--block-space-half) var(--block-space);
position: relative;
text-align: center;
translate: 0 50%;
z-index: 0;
.btn {
--btn-background: var(--card-color);
--btn-border-color: var(--card-color);
--btn-color: var(--color-ink-inverted);
}
}
/* Notches
/* -------------------------------------------------------------------------- */
.card-perma__notch {
align-items: center;
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
display: inline-flex;
inline-size: auto;
gap: var(--inline-space);
justify-content: center;
margin-inline: auto;
position: relative;
text-align: center;
z-index: 0;
}
.card-perma__notch--top {
grid-area: notch-top;
inline-size: 100%;
margin-block-start: -4ch;
max-inline-size: 36ch;
padding-inline: 1ch;
translate: 0 50%;
.btn {
--btn-border-color: var(--card-color);
--btn-color: var(--card-color);
text-align: center;
&:has(input:checked) {
--btn-background: var(--card-color);
--btn-border-color: var(--card-color);
--btn-color: var(--color-ink-inverted);
}
}
}
.card-perma__notch--bottom {
grid-area: notch-bottom;
/* Overlap the card BG by half the button height */
&:has(.btn) {
translate: 0 calc(-1 * var(--half-btn-height));
}
form {
background-color: var(--color-canvas);
border-radius: 99rem;
}
.btn:not(.popup__btn, .btn--plain, .btn--reversed, .settings-subscription__button) {
--btn-background: var(--card-color);
--btn-color: var(--color-ink-inverted);
}
.btn--reversed {
--btn-background: var(--color-canvas);
--btn-color: var(--card-color);
--btn-border-color: var(--color-container);
}
@media (max-width: 639px) {
flex-direction: column;
}
}
.card-perma__notch-new-card-buttons {
display: flex;
gap: var(--inline-space-half);
@media (max-width: 479px) {
flex-direction: column;
.btn {
inline-size: 100%;
}
}
}
.card-perma__closure-message {
color: var(--card-color);
grid-area: closure-message;
margin-block: var(--block-space) var(--block-space-double);
padding-inline: 1ch;
.btn--plain {
--btn-color: var(--card-color);
text-decoration: underline;
}
@media (max-width: 799px) {
margin-block: var(--block-space-half);
translate: 0 calc(-0.5 * var(--half-btn-height));
}
@media (min-width: 800px) {
.card-perma__notch--bottom:has(.btn) ~ & {
margin-block: var(--block-space-half) var(--block-space);
translate: 0 calc(-0.5 * var(--half-btn-height));
}
}
}
.card-perma__account-limit-message {
background-color: var(--color-canvas);
border: 2px solid var(--color-container);
border-radius: 4px;
margin-block-start: calc(var(--padding-block) / -2);
padding: 1ch 2ch;
}
}
================================================
FILE: app/assets/stylesheets/cards.css
================================================
@layer components {
/* Base
/* ------------------------------------------------------------------------ */
.card {
--avatar-size: 2.75em;
--card-bg-color: color-mix(in srgb, var(--card-color) 4%, var(--color-canvas));
--card-content-color: color-mix(in srgb, var(--card-color) 30%, var(--color-ink));
--card-text-color: color-mix(in srgb, var(--card-color) 75%, var(--color-ink));
--card-border: 1px solid color-mix(in srgb, var(--card-color) 33%, var(--color-ink-inverted));
--card-header-space: 1ch;
--card-padding-inline: var(--inline-space-double);
--card-padding-block: var(--block-space);
--border-color: transparent;
--border-radius: 0.2em;
--border-size: 0;
aspect-ratio: var(--card-aspect-ratio, auto);
background-color: var(--card-bg-color);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
display: flex;
flex-direction: column;
inline-size: 100%;
padding: var(--card-padding-block) var(--card-padding-inline);
position: relative;
text-align: start;
z-index: 1;
html[data-theme="dark"] & {
box-shadow: 0 0 0 1px var(--color-ink-lighter);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
box-shadow: 0 0 0 1px var(--color-ink-lighter);
}
}
.popup {
inline-size: 260px;
}
}
/* Header
/* ------------------------------------------------------------------------ */
.card__header {
align-items: center;
border-radius: var(--border-radius) 0 0 0;
display: flex;
flex-wrap: nowrap;
gap: var(--card-header-space);
margin-block-start: calc(-1 * var(--card-padding-block));
margin-inline: calc(-1 * var(--card-padding-inline)) calc(-0.5 * var(--card-padding-inline));
max-inline-size: unset;
min-inline-size: 0;
.card__column-name {
display: none;
}
}
.card__board {
align-items: center;
align-self: start;
background-color: var(--card-color);
border-radius: var(--border-radius) 0 var(--border-radius) 0;
color: var(--color-ink-inverted);
display: inline-flex;
font-weight: 600;
max-inline-size: 100%;
min-inline-size: 0;
padding-block: 0.25lh;
padding-inline: var(--card-padding-inline) 1ch;
position: relative;
transition: background-color 100ms ease-out;
&:has(.btn) {
@media (any-hover: hover) {
&:hover {
background-color: color-mix(in srgb, var(--card-color) 90%, var(--color-ink));
}
}
}
dialog {
inset-block-start: 100%;
}
}
.card__id {
flex-shrink: 0;
.card-perma &:before {
content: "No.";
opacity: 0.5;
}
}
.card__board-name {
align-items: center;
border-inline-start: 1px solid color-mix(in hsl, transparent 75%, currentColor);
color: currentColor;
display: flex;
gap: 0.25ch;
margin-inline-start: var(--card-header-space);
max-inline-size: 100%;
min-inline-size: 0;
padding-inline-start: var(--card-header-space);
text-transform: uppercase;
}
.card__board-picker-button {
inset: 0;
position: absolute;
}
.card__tags {
--btn-color: var(--card-color);
align-items: center;
align-self: stretch;
color: var(--card-text-color);
display: flex;
gap: 0.5ch;
min-inline-size: 0;
[data-controller="dialog"] {
align-items: center;
align-self: stretch;
display: flex;
position: relative;
}
.popup {
--panel-size: 18ch;
inset-block-start: 100%;
}
}
.card__tag-picker {
--panel-border-radius: 2em;
--panel-padding: 0.5em 0.7em;
--panel-size: max-content;
inline-size: auto !important;
inset: 0 auto auto 0;
max-inline-size: var(--panel-size) !important;
position: absolute;
z-index: 2;
&[open] {
display: flex;
}
.input {
--input-padding: 0.2em 0.5em;
inline-size: 18ch;
}
}
.card__tag-picker-button {
font-size: 0.6em;
}
.card__tag {
color: inherit;
font-weight: 600;
min-width: 0;
text-transform: uppercase;
}
/* Body
/* ------------------------------------------------------------------------ */
.card__body {
display: flex;
flex-grow: 1;
gap: 1ch;
inline-size: 100%;
padding-block: calc(var(--card-padding-block) / 2);
@media (min-width: 640px) {
gap: var(--card-padding-inline);
}
}
.card__content {
color: var(--card-content-color);
contain: inline-size;
flex: 2 1 auto;
max-inline-size: 100%;
}
.card__title {
--autosize-block-padding: 0 0.5ch;
--input-border-radius: 0;
--input-color: var(--card-content-color);
--lines: 3;
color: var(--card-content-color);
font-size: var(--text-xx-large);
font-weight: 900;
line-height: 1.15;
text-wrap: balance;
&.overflow-line-clamp {
text-wrap: unset; /* text-wrap: balance breaks -webkit-line-clamp in Safari */
}
.card-field__title {
overflow: hidden; /* prevent scrolling on windows */
padding-block: var(--autosize-block-padding);
&:is(textarea)::placeholder {
color: inherit;
opacity: 0.66;
}
}
.card__title-link {
color: inherit;
}
code {
background-color: var(--color-canvas);
border: 1px solid var(--color-ink-lighter);
border-radius: 0.25ch;
font-family: var(--font-mono);
font-size: smaller;
padding: 0.1ch 0.25ch;
}
}
.card__description {
/* Hide the empty element that Lexical saves when nothing is added to the description <p><br /></p> */
action-text-content p:only-child:has(br:only-child) {
display: none;
}
lexxy-toolbar {
border-block-start: 1px solid var(--lexxy-border-color);
}
& ~ .btn.btn--reversed {
--btn-background: var(--card-color);
--btn-color: var(--color-ink-inverted);
}
& ~ .btn {
--btn-border-color: var(--card-color);
--btn-color: var(--card-color);
}
}
.card__stages {
color: var(--card-text-color);
display: flex;
flex: 0 1 auto;
flex-direction: column;
gap: 2px;
justify-self: end;
max-inline-size: 20ch;
padding-block: var(--block-space-half);
}
/* Footer
/* ------------------------------------------------------------------------ */
/* Card metadata */
.card__meta {
--meta-spacer-block: 0.5ch;
--meta-spacer-inline: 0.75ch;
align-items: center;
color: var(--card-text-color);
display: grid;
font-size: var(--text-x-small);
font-weight: 500;
grid-template-areas:
"avatars-author text-added text-updated avatars-assignees"
"avatars-author text-author text-assignees avatars-assignees";
grid-template-columns: auto auto 1fr auto;
inline-size: fit-content;
text-transform: uppercase;
strong,
.local-time-value {
font-weight: 900;
}
}
/* Assign grid areas */
.card__meta-avatars--author { grid-area: avatars-author; }
.card__meta-avatars--assignees { grid-area: avatars-assignees; }
.card__meta-text--added { grid-area: text-added; }
.card__meta-text--author { grid-area: text-author; }
.card__meta-text--updated { grid-area: text-updated; }
.card__meta-text--assignees { grid-area: text-assignees; }
.card__meta-avatars {
align-self: center;
}
.card__meta-avatars--author {
margin-inline-end: var(--meta-spacer-inline);
}
.card__meta-avatars--assignees {
display: flex;
margin-inline-start: var(--meta-spacer-inline);
.avatar {
margin-inline-end: calc(-1 * var(--meta-spacer-inline));
}
}
.card__assignees-trigger {
background: transparent;
border: none;
padding: 0;
display: flex;
&:focus-visible {
outline: none;
.btn {
box-shadow: 0 0 0 var(--focus-ring-size) var(--focus-ring-color);
}
}
}
.card__meta-text {
line-height: 1;
white-space: nowrap;
.icon {
--icon-size: 0.9em;
margin-inline-end: 0.5ch;
vertical-align: top;
}
}
/* Top */
.card__meta-text:nth-of-type(odd) {
border-block-end: var(--card-border);
padding-block-end: var(--meta-spacer-block);
}
/* Bottom */
.card__meta-text:nth-of-type(even) {
padding-block-start: var(--meta-spacer-block);
}
/* Left */
.card__meta-text:nth-of-type(-n+2) {
border-inline-end: var(--card-border);
padding-inline-end: var(--meta-spacer-inline);
}
/* Right */
.card__meta-text:nth-of-type(n+3) {
padding-inline-start: var(--meta-spacer-inline);
}
@media (max-width: 639px) {
.card__meta {
inline-size: 100%;
}
.card__meta-avatars--author,
.card__meta-avatars--assignees .avatar {
display: none;
}
}
/* Closed stamp
/* ------------------------------------------------------------------------ */
.card__closed {
--stamp-color: oklch(var(--lch-green-medium) / 0.65);
align-items: center;
backdrop-filter: blur(2px);
background-color: color-mix(in srgb, var(--card-bg-color) 90%, transparent);
border-radius: 0.2em;
border: 0.5ch solid var(--stamp-color);
color: var(--color-ink-dark);
display: flex;
flex-direction: column;
font-weight: bold;
inset: auto 0 -1lh auto;
justify-content: center;
max-inline-size: 25ch;
min-inline-size: 16ch;
padding: 1ch;
pointer-events: none;
position: absolute;
rotate: 5deg;
transform-origin: top right;
z-index: 2;
.cards & {
display: none;
.cards--grid &,
.cards--on-deck & {
&.card__closed--system {
display: flex;
}
}
}
}
.card:has(.card__closed),
.card:is(.card--postponed),
.card-perma:has(.card__closed),
.card-perma:has(.card--postponed) {
--card-color: var(--color-card-complete) !important;
.bubble {
display: none;
}
}
.card__closed-title {
color: var(--stamp-color);
font-size: 1.3em;
font-weight: 900;
position: relative;
text-align: center;
text-transform: uppercase;
}
.card__closed-date {
font-family: var(--font-mono);
text-transform: uppercase;
}
.card__closed-by {
border-block-end: 1px dashed currentcolor;
}
/* Misc bits
/* ------------------------------------------------------------------------ */
.card__background {
inset: 0;
position: absolute;
z-index: -1;
img {
block-size: 100%;
border-radius: var(--border-radius);
inline-size: 100%;
object-fit: cover;
object-position: center;
opacity: 1;
transition: filter 0.2s ease-in-out, opacity 0.2s ease-in-out;
}
}
.card__link {
content: "";
inset: 0;
position: absolute;
z-index: -1;
}
.card:nth-child(2n+1) .bubble { --bubble-rotate: -90deg; }
.card:nth-child(3n+1) .bubble { --bubble-rotate: 45deg; }
/* Variants
/* ------------------------------------------------------------------------ */
.card--notification {
--card-color: var(--color-card-default);
--card-padding-inline: 1ch;
--card-padding-block: 1ch;
background-color: var(--color-canvas);
color: var(--color-ink);
&.card--closed {
--card-color: var(--color-card-complete) !important;
}
.card__body {
padding-block-end: 0;
}
.card__board {
font-size: var(--text-xx-small);
padding-block: 0.5ch;
padding-inline-start: var(--inline-space-double);
}
.card__header {
margin-block-start: calc(-1.1 * var(--card-padding-block));
margin-inline: calc(-1 * var(--card-padding-inline));
max-inline-size: unset;
}
.card__timestamp {
opacity: 0.66;
}
.card__notification-body {
font-size: var(--text-x-small);
}
.card__notification-meta {
font-size: var(--text-xx-small);
font-weight: 600;
text-transform: uppercase;
}
.card__notification-mentioner {
background-color: var(--color-highlight);
border-radius: 0.7em 0.2em 0.7em 0.2em;
color: inherit;
display: inline-flex;
padding: 0.1em 0.3em;
}
.card__title {
font-size: var(--text-small);
font-weight: bold;
min-block-size: 0;
}
}
.card__notification-unread-indicator {
--btn-background: var(--color-marker);
--btn-border-color: var(--color-canvas);
--btn-color: var(--color-ink-inverted);
--btn-icon-size: 0.5em;
--btn-padding: 0;
--btn-size: 1.6em;
font-size: var(--text-xx-small);
font-weight: 600;
margin: 2px;
position: relative;
z-index: 1;
.icon {
opacity: 0;
transition: opacity 150ms ease;
}
@media (hover: hover) {
.card:hover & {
--btn-background: var(--color-ink-lightest);
--btn-color: var(--color-ink);
.badge-count { opacity: 0; }
.icon { opacity: 1; }
}
}
}
.card__board-public-description {
max-inline-size: 66ch;
> *:first-child { margin-block-start: 0; }
> *:last-child { margin-block-end: 0; }
ul, ol {
inline-size: fit-content;
margin-inline: auto;
text-align: start;
}
code {
text-align: left;
}
}
}
================================================
FILE: app/assets/stylesheets/circled-text.css
================================================
@layer components {
.circled-text {
--circled-color: oklch(var(--lch-blue-dark));
--circled-padding: -0.5ch;
background: none;
color: var(--circled-color);
position: relative;
white-space: nowrap;
span {
opacity: 0.5;
mix-blend-mode: multiply;
html[data-theme="dark"] & {
mix-blend-mode: screen;
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
mix-blend-mode: screen;
}
}
}
span::before,
span::after {
border: 2px solid var(--circled-color);
content: "";
inset: var(--circled-padding);
position: absolute;
}
span::before {
border-inline-end: none;
border-radius: 100% 0 0 75% / 50% 0 0 50%;
inset-block-start: calc(var(--circled-padding) / 2);
inset-inline-end: 50%;
}
span::after {
border-inline-start: none;
border-radius: 0 100% 75% 0 / 0 50% 50% 0;
inset-inline-start: 30%;
}
}
}
================================================
FILE: app/assets/stylesheets/color-picker.css
================================================
@layer components {
.color-picker__colors {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--inline-space-half);
.btn {
--btn-border-radius: 0.1em;
--btn-size: 2em;
--icon-size: 1.3em;
inline-size: 100%;
}
}
}
================================================
FILE: app/assets/stylesheets/comments.css
================================================
@layer components {
.comments {
--avatar-size: 2.33em;
--comment-padding-block: var(--block-space-half);
--comment-padding-inline: var(--inline-space-double);
--comment-max: 70ch;
--reaction-size: 2.25rem;
display: flex;
flex-direction: column;
padding-inline: var(--inline-space);
place-items: center;
text-align: center;
@media (min-width: 160ch) {
padding-inline: var(--tray-size);
}
@media (min-width: 640px) {
--reaction-size: 1.6875rem;
}
}
.comments__subscribers {
max-inline-size: var(--comment-max);
padding-inline: calc(var(--comment-padding-block) + var(--inline-space-double));
}
.comment {
/* Distinguish from the .comment class used for code formatting without extra specificity */
&:where(.comments &) {
display: flex;
margin-inline: auto;
max-inline-size: var(--comment-max);
position: relative;
}
.comment-by-system & {
--comment-padding-block: var(--block-space-half);
text-align: center;
&::before {
/* Make up space for lack of avatar */
content: "";
display: flex;
inline-size: calc(var(--comment-padding-inline) * 0.9);
}
.comment__avatar {
display: none;
}
.comment__author {
a { margin: 0 auto; }
h3 { margin-inline: auto; }
strong { display: none; }
}
.comment__body {
padding: 0;
text-align: center;
}
.comment__content {
--stripe-color: var(--color-ink-lightest);
background-image: repeating-linear-gradient(
45deg in srgb,
var(--color-canvas) 0 1px,
var(--stripe-color) 1px 10px);
padding-inline: var(--comment-padding-inline);
.comments--system-expanded .comment-by-system & {
--stripe-color: color-mix(in srgb, var(--card-color) 10%, var(--color-canvas));
}
}
.reactions {
display: none !important;
}
}
.reactions {
margin-block-start: var(--block-space-half);
margin-inline: calc(var(--column-gap) / -1);
&:not(:has(.reaction)) {
inset-block-end: var(--comment-padding-block);
inset-inline-end: calc(var(--comment-padding-inline) / 2);
margin: 0;
position: absolute;
@media (max-width: 640px) {
inset-inline-end: calc(var(--comment-padding-inline) / 3);
}
}
}
}
.comment__author {
.btn {
font-weight: inherit;
}
@media (max-width: 639px) {
margin-block-end: calc(var(--block-space-half) / 2);
h3 {
display: flex;
flex-wrap: wrap;
align-items: baseline;
column-gap: 0.4em;
}
}
}
.comment__avatar {
margin: calc(var(--comment-padding-block) * 0.75) calc(var(--comment-padding-inline) * -0.75);
z-index: 0;
}
.comment__body {
text-align: start;
.action-text-content {
> action-text-attachment:first-child figure {
margin-block-start: 0.5ch;
}
> :last-child {
margin-block-end: 0;
}
}
&:not:has(lexxy-editor) {
padding-inline-end: var(--reaction-size);
}
/* Add an empty space so the last line of text doesn't overlap with the reaction button */
.action-text-content > p:last-child::after {
content: "";
display: inline-block;
inline-size: var(--reaction-size);
}
}
.comment__content {
--btn-icon-size: 1.2rem;
--btn-size: var(--reaction-size);
--comment-bg-color: var(--color-ink-lightest);
--lexxy-bg-color: var(--comment-bg-color);
background-color: var(--comment-bg-color);
border-radius: 0.2em;
max-inline-size: calc(100% - calc(var(--comment-padding-inline) * 0.75));
padding:
var(--comment-padding-block)
calc(var(--comment-padding-inline) / 2)
calc(var(--comment-padding-block) * 1.5)
var(--comment-padding-inline);
word-wrap: break-word;
}
.comment__edit {
background-color: var(--color-ink-lightest);
&:hover {
z-index: 1;
}
}
.comment__permalink-title {
color: currentColor;
opacity: 0.66;
text-decoration: none;
text-transform: capitalize;
@media (max-width: 639px) {
font-size: var(--text-small);
}
}
.comment__history {
background-color: transparent;
display: none;
inset: var(--comment-padding-block) var(--comment-padding-block) auto auto;
translate: 2px -2px; /* Align baseline with time stamp */
position: absolute;
@media (any-hover: hover) {
&:hover {
background-color: var(--stripe-color);
}
}
}
.comment-by-system {
display: none;
transition: var(--dialog-duration) allow-discrete;
transition-property: display;
.comments--system-expanded & {
display: contents;
}
}
/* Show the last system comment */
:nth-last-child(1 of .comment-by-system) {
display: contents;
.comment__history {
display: inline-flex;
}
}
/* Hide the "Show history" button if there's only one system comment */
:nth-child(1 of .comment-by-system) {
.comment__history {
display: none;
}
}
.comment-by-system--account-limit {
--stripe-color: oklch(var(--lch-blue-lightest));
.comment__content {
padding: 3ch;
}
}
}
================================================
FILE: app/assets/stylesheets/credentials.css
================================================
@layer components {
.credential {
border-block-start: var(--border);
list-style: none;
&:last-child {
border-block-end: var(--border);
}
}
.credential__link {
align-items: center;
block-size: 1.75lh;
color: currentcolor;
display: flex;
gap: 1ch;
padding-inline: 1ch;
@media (any-hover: hover) {
&:hover {
background: var(--color-ink-lightest);
.credential__arrow {
opacity: 0.66;
}
}
}
}
.credential__arrow {
margin-inline-start: auto;
opacity: 0;
}
[data-passkey-errors] [data-passkey-error] {
display: none;
}
[data-passkey-errors][data-passkey-error-state="error"] [data-passkey-error="error"],
[data-passkey-errors][data-passkey-error-state="cancelled"] [data-passkey-error="cancelled"] {
display: block;
}
}
================================================
FILE: app/assets/stylesheets/dialog.css
================================================
@layer components {
/* Prevent page scrolling when modal dialog is open */
html:has(dialog:modal) {
overflow: hidden;
}
:is(.dialog) {
border: 0;
opacity: 0;
transform: scale(0.85);
transform-origin: center;
transition-behavior: allow-discrete;
transition-duration: calc(var(--dialog-duration) / 2); /* Faster closing */
transition-property: display, opacity, overlay, transform;
transition-timing-function: ease-out;
&::backdrop {
background-color: var(--color-black);
opacity: 0;
transition-behavior: allow-discrete;
transition-duration: calc(var(--dialog-duration) / 2);
transition-property: display, opacity, overlay;
transition-timing-function: ease-out;
}
&[open] {
opacity: 1;
transform: scale(1);
transition-duration: var(--dialog-duration); /* Normal opening speed */
&::backdrop {
opacity: 0.5;
transition-duration: var(--dialog-duration);
}
}
@starting-style {
&[open] {
opacity: 0;
transform: scale(0.85);
}
&[open]::backdrop {
opacity: 0;
}
}
}
/* Ensure padding from viewport edges */
.dialog.panel {
max-inline-size: calc(100vw - var(--inline-space-double) * 2);
}
}
================================================
FILE: app/assets/stylesheets/dividers.css
================================================
@layer components {
.divider {
--divider-color: var(--color-ink-light);
align-items: center;
display: flex;
gap: var(--inline-space);
&:before,
&:after {
background: var(--divider-color);
block-size: var(--divider-size, 1px);
content: "";
flex: 1;
}
}
.divider--fade {
&:before { background: linear-gradient(to right, transparent, var(--divider-color) 50%); }
&:after { background: linear-gradient(to left, transparent, var(--divider-color) 50%); }
}
}
================================================
FILE: app/assets/stylesheets/drag_and_drop.css
================================================
@layer components {
.drag-and-drop__dragged-item {
box-shadow: none;
filter: grayscale(1) brightness(0.97);
opacity: 0.6;
outline: 2px dashed var(--color-selected-dark);
}
.drag-and-drop__hover-container {
--dnd-bg-color: var(--color-selected-light);
--dnd-border-color: var(--color-selected-dark);
background-color: var(--dnd-bg-color);
outline: 2px dashed var(--dnd-border-color);
outline-offset: -2px;
transition: background-color 200ms;
z-index: 1;
}
}
================================================
FILE: app/assets/stylesheets/events.css
================================================
@layer components {
/* Events header
/* ------------------------------------------------------------------------ */
.header--events {
--header-button-count: 1;
@media (min-width: 640px) {
--header-actions-width: 7.25rem !important;
}
}
/* Event column layout
/* ------------------------------------------------------------------------ */
.events {
--events-gap: 1ch;
--events-border: 1px solid var(--color-ink-lighter);
--events-day-header-height: 1.75rem;
}
.events--grid {
--events-grid-gap: 1rem;
--events-grid-columns: 1;
container-type: inline-size;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: var(--events-grid-gap);
justify-content: center;
margin: var(--block-space) auto;
max-inline-size: var(--main-width);
@media (min-width: 640px) {
--events-grid-columns: 2;
}
@media (min-width: 960px) {
--events-grid-columns: 3;
}
.event {
inline-size: calc((100% - var(--events-grid-gap) * (var(--events-grid-columns) - 1) ) / var(--events-grid-columns)) !important;
margin: 0 !important;
}
}
.events__activity-summary {
border: solid var(--color-ink-lighter);
border-width: 1px 1px 0 1px;
color: var(--color-ink-darker);
inline-size: auto;
margin-inline: auto;
padding: 1.1lh 1lh 1lh;
position: relative;
text-align: start;
z-index: 2;
.events section:first-of-type & {
border-radius: 0.5em 0.5em 0 0;
}
&:has(.events__activity--generating) {
--border-color: var(--color-selected-dark);
animation: gradient 4s ease infinite;
background: linear-gradient(-45deg, var(--color-gradient-1), var(--color-gradient-2), var(--color-gradient-3), var(--color-gradient-4));
background-size: 300%;
text-align: center;
}
> * {
column-count: 2;
column-gap: var(--inline-space-double);
margin-inline: auto;
max-inline-size: 80ch;
}
a {
color: inherit;
}
h3 {
column-span: all;
font-size: var(--text-large);
line-height: 1.3;
margin-block: 0.5em 0.25em;
text-align: center;
text-wrap: balance;
+ p {
column-span: all;
font-size: var(--text-medium);
margin-block: 0 1.5em;
text-align: center;
text-wrap: balance;
}
}
h4 {
break-after: avoid-column;
font-size: var(--text-medium);
line-height: 1.3;
margin-block: 0.5em 0.25em;
text-wrap: balance;
+ p {
break-inside: avoid-column;
margin-block: 0 1.5em;
}
}
hr { display: none; }
}
.events__activity-generating-msg {
display: block;
font-weight: 500;
margin-block: 2lh;
opacity: 0.5;
}
.events__activity-prompt-edit {
inset: auto 1em 1em auto;
position: absolute;
}
.events__filter-select {
font-weight: inherit;
text-decoration: underline;
@media (any-hover: hover) {
&:hover {
--btn-color: var(--color-link);
}
}
}
.events__day {
position: relative;
@media (max-width: 639px) {
margin-block-end: calc(var(--events-gap) * 2);
}
}
.events__day-header,
.events__column-header {
font-size: var(--text-small);
text-align: center;
text-transform: uppercase;
}
.events__day-header {
block-size: 0;
margin-block-start: calc(var(--events-day-header-height) / 2);
position: relative;
z-index: var(--z-events-day-header);
}
.events__day-time {
align-items: center;
background-color: var(--color-ink);
block-size: var(--events-day-header-height);
border-radius: 0.2em;
color: var(--color-ink-inverted);
display: flex;
gap: 0.4ch;
inline-size: fit-content;
inset: 0 auto auto 50%;
margin-inline: auto;
padding-inline: 1.5ch;
position: absolute;
translate: -50% -50%;
}
.events__columns {
border-block-start: var(--events-border);
position: relative;
z-index: 1;
@media (min-width: 640px) {
align-items: end;
border-inline: var(--events-border);
display: grid;
grid-template-columns: repeat(3, 1fr);
/* Pseudo column borders since .events__column is display: contents */
&:before,
&:after {
border-inline-start: var(--events-border);
content: "";
inset-block: 0;
position: absolute;
z-index: var(--z-events-day-header);
}
&:before { inset-inline-start: calc(100% / 3); }
&:after { inset-inline-end: calc(100% / 3); }
}
}
.events__column {
@media (max-width: 639px) {
&:not(:has(.event)) {
display: none;
}
}
@media (min-width: 640px) {
display: contents;
}
&:nth-of-type(1) > * { grid-column-start: 1; }
&:nth-of-type(2) > * { grid-column-start: 2; }
&:nth-of-type(3) > * { grid-column-start: 3; }
}
.events__column-header {
background-color: var(--color-canvas);
grid-row-start: 1;
inset-block-start: var(--custom-safe-inset-top);
margin-block: calc(var(--events-gap) * 2) var(--events-gap);
padding-block: var(--events-gap);
position: sticky;
z-index: var(--z-events-column-header);
@media (max-width: 639px) {
margin-inline: calc(var(--main-padding) * -0.5);
padding-inline: var(--main-padding);
}
}
.events__column-footer {
grid-row-start: 26;
margin: var(--events-gap);
padding: var(--block-space-half) var(--inline-space);
}
.events__maximize-button {
inset: 50% var(--events-gap) auto auto;
outline-offset: -2px;
position: absolute;
transform: translateY(-50%);
z-index: 1;
@media (max-width: 639px) {
inset-inline-end: 0;
}
@media (any-hover: hover ) {
opacity: 0;
.events__column-header:hover &,
&:focus-visible {
opacity: 1;
}
}
}
.events__time-block {
align-content: end;
display: grid;
gap: var(--events-gap);
justify-items: center;
margin: 0;
padding: 0;
@media (min-width: 640px) {
padding-block-end: calc(var(--events-gap) * 3);
padding-inline: calc(var(--events-gap) * 2);
}
.event {
grid-column-start: unset !important;
grid-row-start: unset !important;
}
}
.events__none {
background-color: var(--color-canvas);
border-block-start: var(--events-border);
grid-column: 1 / -1;
padding-block: 3em;
text-align: center;
}
/* Event
/* ------------------------------------------------------------------------ */
.event {
--column-gap: 0.7ch;
--event-padding: 0.6em;
background-color: color-mix(in srgb, var(--card-color) 10%, var(--color-canvas));
border-radius: 0.2em;
box-shadow: 0 0 0 1px color-mix(in srgb, var(--card-color) 20%, var(--color-canvas));
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
margin: var(--inline-space);
max-inline-size: 100%;
min-inline-size: 0;
overflow: clip;
padding: var(--event-padding);
position: relative;
z-index: 0;
@media (max-width: 639px) {
inline-size: 100%;
}
&:has(.card__background img:not([src=""])) {
background-color: var(--color-canvas) !important;
}
.event_attachments {
.attachment--image {
block-size: auto;
max-inline-size: 30%;
}
}
.card__background {
filter: brightness(1.2) contrast(0.8);
opacity: 0.2;
z-index: 0;
}
.card__header {
inline-size: 100%;
margin-block-start: calc(-1 * var(--event-padding));
}
.card__board {
background-color: transparent;
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
font-size: 0.7em;
}
.card__board-name {
border-inline-start: 1px solid color-mix(in srgb, var(--color-ink) 33%, var(--color-canvas));
color: color-mix(in srgb, var(--card-color) 40%, var(--color-ink));
margin: 0 0 0 calc(var(--inline-space) * 0.75);
padding-inline-start: calc(var(--inline-space) * 0.75);
}
.card__header {
overflow: visible;
}
.card__id {
margin: 0;
}
}
.event--related {
outline: 0.15rem solid var(--card-color);
}
.event__content {
max-inline-size: 100%;
position: relative;
z-index: 1;
}
.event__grid-item {
background-color: var(--color-canvas);
block-size: 100%;
border-radius: 0;
display: flex;
inline-size: 100%;
}
.event__grid-column-title {
--z: 3;
background-color: var(--color-canvas);
font-size: 0.9em;
padding: 1.5em 0 1em;
text-transform: uppercase;
}
.event__icon {
color: var(--card-color);
display: grid;
margin-inline-start: auto;
place-content: center;
translate: calc(var(--event-padding) / 2);
}
.event__timestamp {
align-self: start;
display: grid;
font-weight: 600;
margin-block-end: var(--block-space-half);
}
.event__title {
--lines: 4;
font-size: 1.1em;
line-height: 1.2;
}
}
================================================
FILE: app/assets/stylesheets/expandable.css
================================================
@layer components {
.expandable-on-native {
body:not([data-platform~=native]) & {
&::details-content {
display: contents;
}
summary {
display: none;
}
}
}
}
================================================
FILE: app/assets/stylesheets/filters.css
================================================
@layer components {
#header:has(.filters) {
position: relative;
}
.filters {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--inline-space-half);
justify-content: center;
padding-block-start: 2px; /* prevents input focus-ring clipping on mobile */
position: relative;
view-transition-name: filters;
z-index: 1;
.btn {
--btn-border-color: var(--color-ink-medium);
--input-background: var(--color-canvas);
}
&:has(dialog[open]),
&:has([data-controller~="tooltip"]:hover) {
z-index: calc(var(--z-nav) + 1);
}
}
.filter {
&[aria-selected] {
display: flex;
}
}
.filter__button {
--btn-border-size: 0;
--btn-font-weight: 400;
--btn-icon-size: 0.7em;
--btn-padding: 0.3em 0.7em;
inline-size: 100%;
justify-content: space-between;
text-align: start;
span {
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
}
img {
display: none;
}
&:has(input[type=checkbox]:checked) img {
display: block;
}
}
.filter__columns {
display: grid;
grid-template-columns: repeat(5, 1fr);
max-block-size: 50dvh;
}
.filter__label {
display: flex;
inline-size: 100%;
padding: 0.3em 0.7em;
strong {
overflow: hidden;
text-decoration: none;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.filter__menu {
display: flex;
flex-direction: column;
inline-size: 100%;
list-style: none;
margin: 0;
min-inline-size: 0;
overflow-x: auto;
padding: 0 var(--inline-space);
position: relative;
row-gap: 0.2em;
&::before {
block-size: 100%;
border-block: 0;
border-inline-end: 0;
border-inline-start: 1px solid var(--color-ink-lighter);
content: "";
display: inline-flex;
inline-size: 0;
position: absolute;
inset: 0 auto 0 0;
}
&:first-child::before {
display: none;
}
li {
text-align: start;
}
}
.filter__terms:is(.input) {
--input-background: var(--color-canvas);
--input-border-radius: 5em;
--input-padding: 0.5em 1.3em;
--input-width: 16em;
--collapsed-filter-space: calc(var(--btn-size) + var(--inline-space-half) + 0.25em);
inline-size: var(--input-width);
min-inline-size: var(--input-width);
.filters:not(.filters--expanded, .filters--has-filters-set) & {
--input-padding: 0.5em 2.7em 0.5em 1.3em;
inline-size: calc(var(--input-width) + (0.25 * var(--collapsed-filter-space)));
margin-inline-end: calc((var(--btn-size) + var(--inline-space-half) + 0.25em) * -1);
min-inline-size: calc(var(--input-width) + (0.25 * var(--collapsed-filter-space)));
}
}
.filter-toggle {
.filters:not(.filters--expanded, .filters--has-filters-set) & {
--btn-background: transparent;
--btn-border-size: 0;
transform: translateX(calc(var(--inline-space-half) * -1));
position: relative;
}
}
.quick-filter {
position: relative;
&:has([aria-checked="true"]):not(.quick-filter--with-default) {
.input--select {
--input-background: var(--color-selected);
}
}
/* Hide a quick filter if there's nothing in it to filter by */
&:not(:has(.popup__item)) {
display: none !important;
}
}
.filters:not(.filters--expanded) {
.quick-filter:not([data-filter-show=true]) {
display: none;
}
}
.filters.filters--expanded {
.quick-filter {
display: block;
}
}
.filters__manage {
display: none;
}
.filters--has-filters-set .filters__manage {
display: flex;
}
.filters__show-when-expanded {
.filters:not(.filters--expanded) & {
display: none;
}
}
.filters__show-when-collapsed {
.filters--expanded & {
display: none;
}
}
}
================================================
FILE: app/assets/stylesheets/flash.css
================================================
@layer components {
.flash {
display: flex;
inset-block-start: calc(var(--block-space) + var(--custom-safe-inset-top));
inset-inline-start: 50%;
justify-content: center;
position: fixed;
transform: translate(-50%);
z-index: var(--z-flash);
}
.flash__inner {
animation: appear-then-fade 3s 300ms both;
background-color: var(--flash-background, var(--color-ink));
border-radius: 4em;
color: var(--flash-color, var(--color-ink-inverted));
display: inline-flex;
font-size: var(--font-size-medium);
inline-size: max-content;
margin: 0 auto;
max-inline-size: 90vw;
padding: 0.7em 1.4em;
}
}
================================================
FILE: app/assets/stylesheets/font-face.css
================================================
@layer base {
/*
Segoe UI Variable Fizzy font face configuration.
1. Segoe UI Variable (Weights 100-700):
Leverages variable font features to:
- Automatically adjust Weight (wght) dynamically within the 100-700 range.
- Automatically manage Optical Size (opsz) based on font-size.
2. Segoe UI Black (Weights 800-900):
Used as a fallback because Segoe UI Variable does not natively support 900 weight.
This ensures a consistent bold experience across all weights.
*/
@font-face {
font-family: "Segoe UI Variable Fizzy";
src: local("Segoe UI Variable");
font-weight: 100 700;
font-style: normal;
}
@font-face {
font-family: "Segoe UI Variable Fizzy";
src: local("Segoe UI Variable");
font-weight: 100 700;
font-style: italic;
}
@font-face {
font-family: "Segoe UI Variable Fizzy";
src: local("Segoe UI Black");
font-weight: 800 900;
font-style: normal;
}
@font-face {
font-family: "Segoe UI Variable Fizzy";
src: local("Segoe UI Black Italic");
font-weight: 800 900;
font-style: italic;
}
}
================================================
FILE: app/assets/stylesheets/golden-effect.css
================================================
@layer components {
.golden-effect {
/* Uncomment below to use the card color for golden effect */
/* --color-golden: color-mix(in srgb, var(--card-color) 35%, transparent); */
background-color: color-mix(in srgb, var(--color-golden) 4%, var(--color-canvas));
background-image: linear-gradient(60deg,
color-mix(in srgb, var(--color-golden) 45%, transparent) 0%,
color-mix(in srgb, var(--color-golden) 10%, transparent) 33%,
color-mix(in srgb, var(--color-golden) 5%, transparent) 66%,
color-mix(in srgb, var(--color-golden) 45%, transparent) 100%
);
box-shadow:
0 0 0 1px color-mix(in oklch, var(--color-golden) 100%, transparent),
0 0 0.2em 0.2em color-mix(in oklch, var(--color-golden) 25%, transparent),
0 0 1em 0.5em color-mix(in oklch, var(--color-golden) 25%, transparent);
lexxy-toolbar {
--lexxy-bg-color: transparent;
}
}
}
================================================
FILE: app/assets/stylesheets/header.css
================================================
@layer components {
/* Centered title with space for two buttons on either side */
.header {
--header-gap: 0.5ch;
--btn-icon-size: 1rem;
--header-btn-size: 2rem;
--header-button-count: 0;
--header-actions-width: calc((var(--header-btn-size) + var(--header-gap)) * var(--header-button-count));
display: grid;
grid-template-columns: var(--header-actions-width) 1fr var(--header-actions-width);
grid-template-areas:
"menu menu menu"
"actions-start title actions-end";
max-inline-size: 100dvw;
padding-block: calc(var(--block-space-half) + var(--custom-safe-inset-top)) var(--block-space-half);
padding-inline: var(--main-padding);
position: relative;
z-index: var(--z-nav);
/* Change the grid size depending on how many buttons are present */
&:has(.header__actions > *:nth-child(1)) { --header-button-count: 1; }
&:has(.header__actions > *:nth-child(2)) { --header-button-count: 2; }
&:has(.header__actions > *:nth-child(3)) { --header-button-count: 3; }
&:has(nav) {
row-gap: 0;
}
&:has(dialog[open]) {
z-index: var(--z-nav-open);
}
&:has(~ #main .card-columns) {
inline-size: 100dvw;
margin-inline: auto;
max-inline-size: var(--main-width);
}
nav {
grid-area: menu;
margin-inline: auto;
}
}
.header__actions {
display: flex;
font-size: var(--text-x-small);
gap: var(--header-gap);
inline-size: var(--header-actions-width);
}
.header__actions--start {
grid-area: actions-start;
margin-inline-end: auto;
}
.header__actions--end {
grid-area: actions-end;
justify-content: flex-end;
margin-inline-start: auto;
}
.header__title {
color: inherit;
font-size: var(--text-large);
font-weight: 900;
grid-area: title;
margin: 0 auto;
min-inline-size: 0;
text-align: center;
}
.header__skip-navigation {
--left-offset: -999em;
inset-block-start: 4rem;
inset-inline-start: var(--left-offset);
position: absolute;
white-space: nowrap;
z-index: 11;
&:focus {
--left-offset: var(--inline-space);
}
}
.header__logo {
color: var(--color-ink);
font-size: 1.2rem;
inline-size: auto;
margin-block-start: 0.1em;
span {
background: var(--color-ink-lightest);
block-size: auto;
border-radius: 0.3125em;
box-shadow:
0 0 0 1px oklch(var(--lch-ink-darkest) / 0.1),
0 0.1em 0.2em -0.1em oklch(var(--lch-ink-darkest) / 0.05),
0 0.2em 0.4em -0.2em oklch(var(--lch-ink-darkest) / 0.05),
0 0.3em 0.6em -0.3em oklch(var(--lch-ink-darkest) / 0.05)
;
display: grid;
height: 1.5em;
inline-size: 1.5em;
padding: 0.325em 0.275em 0.225em 0.275em;
place-content: center;
width: 1.5em;
}
svg {
height: 100%;
margin-inline-start: 0.4125em;
margin-inline-end: 0.5375em;
max-height: 0.8625em;
overflow: visible;
width: auto;
}
}
/* Optional class to stack header actions on small screens
/* ------------------------------------------------------------------------ */
.header--mobile-actions-stack {
@media (max-width: 639px) {
grid-template-areas:
"actions-start menu actions-end"
"title title title";
.header__title {
margin-block-start: 0.25rem;
}
}
}
}
================================================
FILE: app/assets/stylesheets/icons.css
================================================
@layer components {
.icon {
-webkit-touch-callout: none;
background-color: currentColor;
block-size: var(--icon-size, 1em);
display: inline-block;
flex-shrink: 0;
inline-size: var(--icon-size, 1em);
mask-image: var(--svg);
mask-position: center;
mask-repeat: no-repeat;
mask-size: var(--icon-size, 1em);
pointer-events: none;
user-select: none;
}
img.icon {
background: none;
}
.icon--37signals { --svg: url("37signals.svg"); }
.icon--add { --svg: url("add.svg "); }
.icon--add--meta { --svg: url("add--meta.svg "); }
.icon--arrow-left { --svg: url("arrow-left.svg "); }
.icon--arrow-right { --svg: url("arrow-right.svg "); }
.icon--arrow-up { --svg: url("arrow-up.svg "); }
.icon--art { --svg: url("art.svg "); }
.icon--assigned { --svg: url("assigned.svg "); }
.icon--attachment { --svg: url("attachment.svg "); }
.icon--authentication { --svg: url("authentication.svg "); }
.icon--bell-alert { --svg: url("bell-alert.svg "); }
.icon--bell-off { --svg: url("bell-off.svg "); }
.icon--bell { --svg: url("bell.svg "); }
.icon--bolt { --svg: url("bolt.svg "); }
.icon--bookmark-outline { --svg: url("bookmark-outline.svg "); }
.icon--bookmark { --svg: url("bookmark.svg "); }
.icon--boost { --svg: url("boost.svg "); }
.icon--camera { --svg: url("camera.svg "); }
.icon--caret-down { --svg: url("caret-down.svg "); }
.icon--check { --svg: url("check.svg "); }
.icon--check-circle { --svg: url("check-circle.svg "); }
.icon--check-all { --svg: url("check-all.svg "); }
.icon--clipboard { --svg: url("clipboard.svg "); }
.icon--close { --svg: url("close.svg "); }
.icon--close-circle { --svg: url("close-circle.svg "); }
.icon--collapse { --svg: url("collapse.svg "); }
.icon--board { --svg: url("board.svg "); }
.icon--board-add { --svg: url("board-add.svg "); }
.icon--column-left { --svg: url("column-left.svg "); }
.icon--column-right { --svg: url("column-right.svg "); }
.icon--comment { --svg: url("comment.svg "); }
.icon--copy-paste { --svg: url("copy-paste.svg "); }
.icon--crown { --svg: url("crown.svg "); }
.icon--email { --svg: url("email.svg "); }
.icon--everyone { --svg: url("everyone.svg "); }
.icon--expand { --svg: url("expand.svg "); }
.icon--gear { --svg: url("gear.svg "); }
.icon--grid { --svg: url("grid.svg "); }
.icon--filter { --svg: url("filter.svg "); }
.icon--fizzy { --svg: url("fizzy.svg"); }
.icon--globe { --svg: url("globe.svg "); }
.icon--golden-ticket { --svg: url("golden-ticket.svg "); }
.icon--history { --svg: url("history.svg "); }
.icon--home { --svg: url("home.svg "); }
.icon--install-edge { --svg: url("install-edge.svg "); }
.icon--lifebuoy { --svg: url("lifebuoy.svg "); }
.icon--lock { --svg: url("lock.svg "); }
.icon--logout { --svg: url("logout.svg "); }
.icon--marker { --svg: url("marker.svg "); }
.icon--maximize { --svg: url("maximize.svg "); }
.icon--menu { --svg: url("menu.svg "); }
.icon--menu-dots-horizontal { --svg: url("menu-dots-horizontal.svg "); }
.icon--menu-dots-vertical { --svg: url("menu-dots-vertical.svg "); }
.icon--minus { --svg: url("minus.svg "); }
.icon--monitor { --svg: url("monitor.svg "); }
.icon--moon { --svg: url("moon.svg "); }
.icon--move { --svg: url("move.svg "); }
.icon--notification-bell-access-only { --svg: url("bell.svg "); }
.icon--notification-bell-watching { --svg: url("bell-off.svg "); }
.icon--notification-bell-reverse-access-only { --svg: url("bell-off.svg "); }
.icon--notification-bell-reverse-watching { --svg: url("bell.svg "); }
.icon--password { --svg: url("password.svg "); }
.icon--pencil { --svg: url("pencil.svg "); }
.icon--person { --svg: url("person.svg "); }
.icon--person-add { --svg: url("person-add.svg "); }
.icon--picture-add { --svg: url("picture-add.svg "); }
.icon--picture-double { --svg: url("picture-double.svg "); }
.icon--picture-remove { --svg: url("picture-remove.svg "); }
.icon--picture-zoom { --svg: url("picture-zoom.svg "); }
.icon--pinned { --svg: url("pinned.svg "); }
.icon--qr-code { --svg: url("qr-code.svg "); }
.icon--reaction { --svg: url("reaction.svg "); }
.icon--refresh { --svg: url("refresh.svg "); }
.icon--refresh--meta { --svg: url("refresh--meta.svg "); }
.icon--remove { --svg: url("remove.svg "); }
.icon--rename { --svg: url("rename.svg "); }
.icon--search { --svg: url("search.svg "); }
.icon--settings { --svg: url("settings.svg "); }
.icon--share { --svg: url("share.svg "); }
.icon--sliders { --svg: url("sliders.svg "); }
.icon--sun { --svg: url("sun.svg "); }
.icon--switch { --svg: url("switch.svg "); }
.icon--tag { --svg: url("tag.svg "); }
.icon--tag-outline { --svg: url("tag-outline.svg "); }
.icon--thumb-up { --svg: url("thumb-up.svg "); }
.icon--trash { --svg: url("trash.svg "); }
.icon--unpinned { --svg: url("unpinned.svg"); }
.icon--unseen { --svg: url("unseen.svg"); }
.icon--world { --svg: url("world.svg"); }
.icon--youtube { --svg: url("youtube.svg"); }
}
================================================
FILE: app/assets/stylesheets/import.css
================================================
@layer components {
.import-status {
--import-status-border-color: var(--color-ink-light);
--import-status-color: var(--color-ink);
border: 1px dashed var(--import-status-border-color);
border-radius: 1ch;
color: var(--import-status-color);
font-size: var(--text-medium);
padding: 1.5ch;
.btn {
font-size: var(--text-small);
margin-block-start: 1.5ch;
}
}
.import-status--success {
--import-status-border-color: var(--color-positive);
--import-status-color: var(--color-positive);
}
.import-status--error {
--import-status-border-color: var(--color-negative);
--import-status-color: var(--color-negative);
}
@keyframes dash {
to {
background-position: 100% 0%, 0% 100%, 0% 0%, 100% 100%;
}
}
}
================================================
FILE: app/assets/stylesheets/inputs.css
================================================
@layer components {
/* Text inputs */
.input {
accent-color: var(--input-accent-color, var(--color-ink));
background-color: var(--input-background, transparent);
border-radius: var(--input-border-radius, 0.5em);
border: var(--input-border-size, 1px) solid var(--input-border-color, var(--color-ink-medium));
color: var(--input-color, var(--color-ink));
font-size: max(16px, 1em);
inline-size: 100%;
line-height: inherit;
max-inline-size: 100%;
padding: var(--input-padding, 0.5em 0.8em);
resize: none;
&:autofill,
&:-webkit-autofill,
&:-webkit-autofill:hover,
&:-webkit-autofill:focus {
-webkit-text-fill-color: var(--color-ink);
-webkit-box-shadow: 0 0 0px 1000px var(--color-selected) inset;
}
&:where(:focus) {
--focus-ring-offset: -1px;
}
&[readonly] {
--focus-ring-size: 0;
}
&[autocomplete='one-time-code'] {
--input-spacing: 0.5em;
font-family: var(--font-mono);
font-size: var(--text-large);
font-weight: 900;
inline-size: 18ch;
letter-spacing: 1ch;
min-inline-size: 18ch;
text-align: center;
}
&[type='number'] {
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
/* Target mobile Safari only */
@supports (hanging-punctuation: first) and (font: -apple-system-body) and (-webkit-appearance: none) {
@media (hover: none) {
font-size: max(16px, 1em) !important;
}
}
}
.input--file,
.input--upload {
cursor: pointer;
&:has(input[type="file"]:focus-visible) {
outline: 0.15rem solid var(--color-selected-dark);
}
input[type="file"] {
--hover-size: 0;
--input-border-color: transparent;
--input-border-radius: 8px;
block-size: 100%;
cursor: pointer;
font-size: 0;
inline-size: 100%;
overflow: clip;
&::file-selector-button {
appearance: none;
cursor: pointer;
opacity: 0;
}
}
}
.input--file {
display: grid;
inline-size: auto;
place-items: center;
> * {
grid-area: 1 / 1;
}
img {
border-radius: 0.4em;
}
&:is(.avatar) {
input[type="file"] {
border-radius: 50%;
}
}
}
.input--upload {
--btn-border-color: var(--color-ink);
--btn-border-radius: 1ch;
border-style: dashed;
position: relative;
input[type="file"] {
inset: 0;
outline: none;
position: absolute;
}
&:has([data-upload-preview-target="fileName"]:not([hidden])) {
--btn-border-color: var(--color-positive);
--btn-color: var(--color-positive);
}
}
.input--select {
--input-border-radius: 2em;
--input-padding: 0.5em 1.8em 0.5em 1.2em;
--caret-icon: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23000'/%3E%3C/svg%3E");
--caret-icon-dark: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m12 19.5c-.7 0-1.3-.3-1.7-.8l-9.8-11.1c-.7-.8-.6-1.9.2-2.6.8-.6 1.9-.6 2.5.2l8.6 9.8c0 .1.2.1.4 0l8.6-9.8c.7-.8 1.8-.9 2.6-.2s.9 1.8.2 2.6l-9.8 11.1c-.4.5-1.1.8-1.7.8z' fill='%23fff'/%3E%3C/svg%3E");
-webkit-appearance: none;
appearance: none;
background-image: var(--caret-icon);
background-size: 0.5em;
background-position: center right 0.9em;
background-repeat: no-repeat;
text-align: start;
html[data-theme="dark"] & {
--caret-icon: var(--caret-icon-dark);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
--caret-icon: var(--caret-icon-dark);
}
}
option {
background-color: var(--color-canvas);
color: var(--color-ink);
}
}
.input--textarea {
--input-padding: 0;
line-height: inherit;
min-block-size: calc(3lh + (2 * var(--input-padding)));
min-inline-size: 100%;
padding-block: var(--input-padding);
padding-inline: calc(var(--input-padding) + calc((1lh - 1ex) / 2));
@supports (field-sizing: content) {
field-sizing: content;
max-block-size: calc(3lh + (2 * var(--input-padding)));
min-block-size: calc(1lh + (2 * var(--input-padding)));
}
}
.input--invisible {
background-color: transparent;
block-size: 5px;
border: none;
inline-size: 5px;
opacity: 0.1;
&:focus {
outline: none;
}
}
/* Switches */
.switch {
--switch-color: var(--color-ink-medium);
--switch-hover-brightness: 0.9;
block-size: 1.75em;
border-radius: 2em;
display: inline-flex;
inline-size: 3em;
position: relative;
&:has(:focus-visible) {
.switch__btn {
outline: var(--focus-ring-size) solid var(--focus-ring-color);
}
}
}
.switch__input {
block-size: 0;
inline-size: 0;
opacity: 0.1;
}
.switch__btn {
background-color: var(--switch-color);
border-radius: 2em;
cursor: pointer;
inset: 0;
outline-offset: var(--focus-ring-offset);
position: absolute;
transition: 150ms ease;
&::before {
background-color: var(--color-ink-inverted);
block-size: 1.35em;
border-radius: 50%;
content: "";
inline-size: 1.35em;
inset-block-end: 0.2em;
inset-inline-start: 0.2em;
position: absolute;
transition: 150ms ease;
}
@media (any-hover: hover) {
&:hover {
background-color: color-mix(in srgb, var(--switch-color) 80%, var(--color-ink));
}
}
.switch__input:checked + & {
--switch-color: var(--color-link);
&::before {
transform: translateX(1.2em);
}
}
.switch__input:disabled + & {
--switch-color: var(--color-ink-medium);
cursor: not-allowed;
opacity: 0.5;
}
}
/* Containers that act like (and contain) inputs */
.input--actor {
outline-offset: -1px;
transition: box-shadow 150ms ease, outline-offset 150ms ease;
&:focus-within {
--input-border-color: var(--color-selected-dark);
outline: var(--focus-ring-size) solid var(--focus-ring-color);
}
.input {
--input-padding: 0;
--input-border-radius: 0;
--input-background: transparent;
--input-border-size: 0;
inline-size: 100%;
outline: 0;
}
&:has(.input:is(
:autofill,
:-webkit-autofill,
:-webkit-autofill:hover,
:-webkit-autofill:focus)) {
-webkit-text-fill-color: var(--color-text);
-webkit-box-shadow: 0 0 0px 1000px var(--color-selected) inset;
}
}
.input--hidden {
block-size: 0;
inline-size: 0;
opacity: 0;
padding: 0;
}
.input.boost__input {
--input-border-radius: 0;
--input-border-size: 0;
--input-padding: 0;
color: inherit;
font-size: inherit;
font-weight: inherit;
inline-size: min-content;
max-inline-size: 3ch;
min-inline-size: 1ch;
outline: none;
@supports (field-sizing: content) {
field-sizing: content;
max-inline-size: unset;
}
&:focus {
background-color: var(--color-highlight);
}
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
}
================================================
FILE: app/assets/stylesheets/ios.css
================================================
@layer platform {
:root:has([data-platform~=ios]) {
&[data-text-size=xsmall] { font-size: 14px; }
&[data-text-size=small] { font-size: 15px; }
&[data-text-size=medium] { font-size: 16px; }
&[data-text-size=large] { font-size: 17px; }
&[data-text-size=xlarge] { font-size: 19px; }
&[data-text-size=xxlarge] { font-size: 21px; }
&[data-text-size=xxxlarge] { font-size: 23px; }
}
[data-platform~=ios] {
.hide-on-ios {
display: none;
}
/* Events
/* ------------------------------------------------------------------------ */
.events__column-header {
background-color: unset;
& > span {
display: inline-block;
position: relative;
&::before {
content: "";
display: block;
background-color: oklch(from var(--color-canvas) l c h / 0.8);
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
border-radius: 10em;
position: absolute;
inset-inline: -1.5ch;
inset-block: calc(var(--events-gap) * -0.8);
z-index: -1;
}
}
}
}
}
================================================
FILE: app/assets/stylesheets/knobs.css
================================================
@layer components {
.knob {
--knob-angle-reserve: 120deg;
--knob-option-angle: calc((360deg - var(--knob-angle-reserve)) / (var(--knob-options) - 1));
--knob-option-size: 3ch;
--knob-chamfer-size: 1ch;
--knob-color: oklch(var(--lch-ink-light));
--knob-color-accent: oklch(var(--lch-blue-medium));
--knob-tick-size: 1ch;
--knob-radius: calc(var(--knob-size) / 2);
--knob-size: 96px;
border: none;
display: block;
font-weight: 500;
padding: var(--knob-option-size) 0 0;
position: relative;
text-align: center;
}
.knob__slider {
appearance: none;
background-color: transparent;
block-size: var(--knob-size);
inline-size: var(--knob-size);
inset: 50% auto auto 50%;
opacity: 0;
position: absolute;
translate: -50% -50%;
z-index: 1;
&::-moz-range-track {
block-size: var(--knob-size);
cursor: grab;
}
&::-webkit-slider-runnable-track {
block-size: var(--knob-size);
cursor: grab;
}
&::-moz-range-thumb {
background-color: transparent;
border: none;
border-radius: 0;
}
&::-webkit-slider-thumb {
appearance: none;
background-color: transparent;
height: 1px;
width: 1px;
}
}
.knob__option {
block-size: var(--knob-option-size);
border-radius: 50%;
cursor: pointer;
display: grid;
inline-size: var(--knob-option-size);
inset: 50% auto auto 50%;
place-content: center;
position: absolute;
transform:
translate(-50%, -50%)
rotate(calc(-1 * ((360deg - var(--knob-angle-reserve)) / 2) + (var(--knob-option-angle) * var(--i))))
translateY(calc(-1 * var(--knob-radius) - 50% - var(--knob-tick-size)));
z-index: 1;
&:hover,
&:has(input:checked) {
color: var(--knob-color-accent);
}
&:has(:focus-visible) {
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: 0;
}
/* Tick marks */
&:before {
background-color: var(--knob-color);
block-size: var(--knob-tick-size);
content: "";
inline-size: 2px;
inset: 100% auto auto 50%;
position: absolute;
translate: -50% 0;
}
/* The value text */
span {
rotate: calc(((360deg - var(--knob-angle-reserve)) / 2) - (var(--knob-option-angle) * var(--i)));
}
input {
opacity: 0;
position: absolute;
}
}
.knob__knob {
background: linear-gradient(to top, var(--knob-color), color-mix(in oklch, var(--knob-color) 50%, var(--color-canvas) 50%));
block-size: var(--knob-size);
border-radius: 50%;
box-shadow:
0 0 2px 1px rgba(0,0,0,0.10),
0 2px 4px rgba(0,0,0,0.15),
0 2px 8px rgba(0,0,0,0.20);
inline-size: var(--knob-size);
margin-inline: auto;
position: relative;
&:before,
&:after {
content: "";
position: absolute;
}
/* Indent */
&:before {
background: linear-gradient(to bottom, var(--knob-color), color-mix(in oklch, var(--knob-color) 50%, var(--color-canvas) 50%));
block-size: calc(var(--knob-size) - var(--knob-chamfer-size));
border-radius: 50%;
box-shadow:
0 -1px 0 rgba(255, 255, 255, 0.25),
inset 0 -1px 0 rgba(255, 255, 255, 0.25);
inline-size: calc(var(--knob-size) - var(--knob-chamfer-size));
inset: 50% auto auto 50%;
translate: -50% -50%;
}
/* Indicator */
&:after {
background-color: var(--color-ink);
block-size: calc(var(--knob-radius) - var(--knob-chamfer-size) / 2);
border-radius: 50% 50% 2px 2px;
inline-size: 4px;
inset: auto auto 50% 50%;
rotate: calc(-1 * ((360deg - var(--knob-angle-reserve)) / 2) + (var(--knob-option-angle) * var(--knob-index)));
transform-origin: center bottom;
transition: rotate 100ms;
translate: -50% 0;
}
}
.knob__label {
font-weight: bold;
margin-block-start: 1ch;
text-transform: uppercase;
}
}
================================================
FILE: app/assets/stylesheets/layout.css
================================================
@layer base {
body {
display: grid;
grid-template-rows: auto 1fr auto 9em;
&.public {
grid-template-rows: auto 1fr auto;
}
&.compact-on-touch {
@media (any-hover: none) {
grid-template-rows: auto 1fr auto;
min-height: unset;
}
}
}
/* Required for the card column page on mobile, but not needed otherwise */
:where(#global-container) {
display: contents;
}
:where(#header) {
position: relative;
z-index: var(--z-nav);
}
:where(#main) {
inline-size: 100dvw;
margin-inline: auto;
max-inline-size: 100dvw;
padding-inline:
calc(var(--main-padding) + var(--custom-safe-inset-left))
calc(var(--main-padding) + var(--custom-safe-inset-right));
text-align: center;
}
:where(#footer) {
max-inline-size: 100dvw;
}
:is(#header, #footer) {
@media print {
display: none;
}
}
}
================================================
FILE: app/assets/stylesheets/lexxy.css
================================================
@import url("lexxy-variables.css") layer(base);
@import url("lexxy-content.css") layer(base);
@import url("lexxy-editor.css") layer(base);
:root {
--lexxy-color-ink: var(--color-ink);
--lexxy-color-ink-medium: var(--color-ink-dark);
--lexxy-color-ink-light: var(--color-ink-medium);
--lexxy-color-ink-lighter: var(--color-ink-light);
--lexxy-color-ink-lightest: var(--color-ink-lighter);
--lexxy-color-ink-inverted: var(--color-ink-inverted);
--lexxy-color-canvas: var(--color-canvas);
--lexxy-color-accent-dark: var(--color-ink-dark);
--lexxy-color-accent-medium: var(--color-ink-medium);
--lexxy-color-accent-light: var(--color-ink-light);
--lexxy-color-accent-lightest: var(--color-ink-lighter);
--lexxy-color-red: oklch(var(--lch-red-medium));
--lexxy-color-green: oklch(var(--lch-green-medium));
--lexxy-color-blue: oklch(var(--lch-blue-medium));
--lexxy-color-purple: oklch(var(--lch-purple-medium));
--lexxy-color-code-token-att: var(--color-code-token__att);
--lexxy-color-code-token-comment: var(--color-code-token__comment);
--lexxy-color-code-token-function: var(--color-code-token__function);
--lexxy-color-code-token-operator: var(--color-code-token__operator);
--lexxy-color-code-token-property: var(--color-code-token__property);
--lexxy-color-code-token-punctuation: var(--color-code-token__punctuation);
--lexxy-color-code-token-selector: var(--color-code-token__selector);
--lexxy-color-code-token-variable: var(--color-code-token__variable);
--lexxy-color-selected: oklch(var(--lch-blue-light));
--lexxy-color-selected-dark: oklch(var(--lch-blue-medium));
--lexxy-color-table-cell-border: var(--color-ink-ligher);
--lexxy-color-table-cell-selected-bg: var(--lexxy-color-selected);
--lexxy-color-table-cell-toggle: var(--lexxy-color-selected);
--lexxy-color-table-cell-remove: oklch(var(--lch-red-medium) / 20%);
--lexxy-focus-ring-offset: 2px;
}
@layer components {
/* Editor
/* ------------------------------------------------------------------------ */
lexxy-editor {
--lexxy-border-color: oklch(var(--lch-ink-darkest) / 20%);
--lexxy-editor-padding: 0;
--lexxy-toolbar-button-size: 2rem;
background-color: transparent;
border: none;
border-radius: 0;
}
lexxy-toolbar {
border-color: var(--lexxy-border-color);
gap: 0;
}
.lexxy-editor__toolbar-button {
background: transparent;
&[aria-pressed="true"],
[open] > & {
background-color: oklch(var(--lch-blue-medium) / 20%);
}
@media(any-hover: hover) {
&:hover:not([aria-pressed="true"]) {
background-color: oklch(var(--lch-ink-dark) / 20%);
}
}
}
lexxy-link-dropdown {
--lexxy-toolbar-spacing: 1.5ch;
.lexxy-editor__toolbar-button {
border-radius: 99rem;
@media(any-hover: hover) {
&:hover {
filter: brightness(0.9);
opacity: 1;
}
}
&[type="submit"],
&[type="submit"]:hover {
background-color: var(--color-link);
}
&[type="button"] {
border: 1px solid var(--color-ink-light);
}
}
}
.lexxy-editor__content {
margin-block-start: 0.5lh;
}
.lexxy-code-language-picker {
border-radius: 99rem;
}
lexxy-table-tools {
font-size: var(--text-x-small);
}
[data-lexical-cursor] {
animation: blink 1s step-end infinite;
block-size: 1lh;
border-inline-start: 1px solid currentColor;
line-height: inherit;
margin-block: 1em;
}
.lexxy-prompt-menu {
max-inline-size: min(35ch, calc(100% - var(--lexxy-prompt-offset-x)));
}
/* Content
/* ------------------------------------------------------------------------ */
.lexxy-content {
--lexxy-content-margin: 0.5lh;
color: currentColor;
h1, h2, h3, h4, h5, h6 {
font-weight: 800;
letter-spacing: -0.02ch;
line-height: 1.1;
overflow-wrap: break-word;
text-wrap: balance;
}
p:has(+ p) {
margin: 0;
}
ol, ul {
&:not(.lexxy-prompt-menu) {
padding-inline-start: 1ch;
}
}
blockquote {
border-inline-start: 0.25em solid var(--color-ink-lighter);
padding-block: 0;
}
code {
background: var(--color-canvas);
border: 1px solid var(--color-ink-lighter);
}
.horizontal-divider {
padding-block: var(--lexxy-content-margin);
hr { margin: 0; }
}
hr {
border: 0;
border-block-end: 2px solid currentColor;
color: currentColor;
inline-size: 20%;
margin: calc(var(--lexxy-content-margin) * 2) 0;
}
table {
th, td {
font-size: var(--text-small);
padding-block: 0.75ch;
}
tr:not([data-action="delete"]) {
th:not([class*="selected"], [data-action="delete"], [data-action="toggle"]) { background-color: var(--color-ink-lightest); }
td:not([class*="selected"], [data-action="delete"], [data-action="toggle"]) { background-color: var(--color-canvas); }
}
}
.attachment {
margin-inline: auto;
}
}
.attachment {
margin-inline: 0;
}
.attachment-gallery {
.attachment {
display: inline-block;
inline-size: calc(33.333% - 0.8ch);
}
&.attachment-gallery--2,
&.attachment-gallery--4 {
.attachment {
inline-size: calc(50% - 0.8ch);
}
}
}
action-text-attachment[content-type^='application/vnd.actiontext'] {
lexxy-node-delete-button {
inset-inline-start: -0.25ch;
.lexxy-floating-controls__group {
background-color: oklch(var(--lch-blue-dark));
border-radius: 50%;
}
}
}
}
================================================
FILE: app/assets/stylesheets/lightbox.css
================================================
@layer components {
.lightbox {
--dialog-duration: 350ms;
--lightbox-padding: 3vmin;
background-color: transparent;
block-size: 100dvh;
border: 0;
inline-size: 100dvw;
inset: 0;
margin: auto;
max-height: unset;
max-width: unset;
overflow: hidden;
padding:
calc(var(--lightbox-padding) + var(--custom-safe-inset-top))
calc(var(--lightbox-padding) + var(--custom-safe-inset-right))
calc(var(--lightbox-padding) + var(--custom-safe-inset-bottom))
calc(var(--lightbox-padding) + var(--custom-safe-inset-left));
text-align: center;
&::backdrop {
-webkit-backdrop-filter: blur(16px);
backdrop-filter: blur(16px);
background-color: oklch(var(--lch-black) / 50%);
}
/* Closed state */
&,
&::backdrop {
opacity: 0;
transition: var(--dialog-duration) allow-discrete;
transition-property: display, opacity, overlay;
}
/* Open state */
&[open],
&[open]::backdrop {
align-items: center;
display: flex;
justify-content: center;
opacity: 1;
@starting-style {
opacity: 0;
}
.lightbox__figure {
animation: slide-up var(--dialog-duration);
}
}
}
.lightbox__actions {
display: flex;
gap: 1ch;
inset:
calc(var(--lightbox-padding) + var(--custom-safe-inset-top))
calc(var(--lightbox-padding) + var(--custom-safe-inset-right))
auto
auto;
position: absolute;
}
.lightbox__figure {
animation-fill-mode: forwards;
animation: slide-down var(--dialog-duration);
display: flex;
flex-direction: column;
gap: var(--lightbox-padding);
margin: 0 auto;
max-block-size: 100%;
img {
object-fit: contain;
}
}
.lightbox__caption {
color: var(--color-white);
&:empty {
display: none;
}
&[tabindex="-1"]:focus-visible {
outline: unset;
}
}
.lightbox__image {
flex: 1;
min-block-size: 0;
}
/* Prevent body from scrolling when lightbox is open */
html:has(.lightbox[open]) {
overflow: clip;
}
}
================================================
FILE: app/assets/stylesheets/markdown.css
================================================
@layer components {
.heading__link {
--opacity: 0.5;
--size: 0.8em;
background: url(link.svg) no-repeat center bottom 0.2em;
background-size: var(--size);
block-size: 1em;
color: var(--color-link);
display: inline-flex;
font-size: var(--size);
inline-size: var(--size);
padding: 1em 0 0;
opacity: var(--opacity);
overflow: hidden;
transition: opacity 300ms ease;
vertical-align: middle;
@media (hover: hover) {
--opacity: 0;
:is(h1, h2, h3, h4, h5, h6):hover & {
--opacity: 0.5;
}
}
html[data-theme="dark"] & {
filter: invert(1);
}
@media (prefers-color-scheme: dark) {
html:not([data-theme]) & {
filter: invert(1);
}
}
}
}
================================================
FILE: app/assets/stylesheets/native.css
================================================
@layer native {
[data-platform~=native] {
--footer-height: 0;
-webkit-tap-highlight-color: transparent;
.hide-on-native {
display: none;
}
/* Layout
/* ------------------------------------------------------------------------ */
&:not(.contained-scrolling) {
#main {
padding-block-end: var(--custom-safe-inset-bottom);
}
}
/* Header
/* ------------------------------------------------------------------------ */
.header {
padding-block-start: var(--custom-safe-inset-top);
}
.header--mobile-actions-stack {
.header__title {
margin-block-start: 0;
}
}
.header:is(
:not(:has(.header__title, .header__actions)),
:not(:has(.header__title, .header__actions--end)):has(.header__actions--start .btn--back:only-child)
) {
block-size: var(--custom-safe-inset-top);
padding: unset;
* {
display: none;
}
}
.header__actions {
.btn--back {
display: none;
}
}
/* Card columns
/* ------------------------------------------------------------------------ */
.board-tools.card {
padding-block-start: 0;
}
/* Card perma
/* ------------------------------------------------------------------------ */
.card-perma {
margin-block-start: var(--block-space-half);
&:not(:has(.card-perma__notch-new-card-buttons)) .card-perma__bg {
padding-block-end: clamp(0.25rem, 2vw, var(--padding-block));
}
.card {
background: linear-gradient(to bottom, var(--color-canvas), var(--card-bg-color));
box-shadow: unset;
}
.card__board {
border-radius: 0 var(--border-radius) var(--border-radius) 0;
}
}
.card-perma__bg {
padding-inline: 0;
padding-block-start: 0;
background-color: unset;
}
.card-perma__closure-message {
margin-block: var(--block-space);
translate: unset;
}
.card-perma--draft {
.card {
box-shadow: 0 101vh 0 100vh var(--card-bg-color);
}
.card-perma__notch--bottom {
z-index: 1;
}
}
/* Search
/* ------------------------------------------------------------------------ */
.search {
overscroll-behavior: auto;
}
.search__title {
text-decoration: none;
}
}
}
[data-bridge-components~=form] {
[data-controller~=bridge--form] {
[data-bridge--form-target~=submit] {
display: none;
}
}
}
[data-bridge-components~=overflow-menu] {
[data-controller~=bridge--overflow-menu] {
[data-bridge--overflow-menu-target~=item] {
display: none;
}
}
}
[data-bridge-components~=buttons] {
[data-bridge--buttons-target~=button] {
display: none;
}
}
[data-bridge-components~=stamp] {
[data-controller~=bridge--stamp] {
display: none;
}
}
================================================
FILE: app/assets/stylesheets/nav.css
================================================
@layer components {
/* Trigger
/* ------------------------------------------------------------------------ */
.nav__trigger {
--input-background: transparent;
--input-border-color: transparent;
--input-padding: 0.3em 2em 0.3em 0.75em;
font-weight: 600;
inline-size: auto;
margin-block-start: 0.1em;
::-webkit-search-cancel-button {
-webkit-appearance: none;
}
@media (any-hover: hover) {
&:hover {
--input-background: var(--color-ink-lighter);
span {
background: var(--color-ink-lighter);
}
}
}
span {
background: var(--color-ink-lightest);
block-size: auto;
border-radius: 0.3125em;
box-shadow:
0 0 0 1px oklch(var(--lch-ink-darkest) / 0.1),
0 0.1em 0.2em -0.1em oklch(var(--lch-ink-darkest) / 0.05),
0 0.2em 0.4em -0.2em oklch(var(--lch-ink-darkest) / 0.05),
0 0.3em 0.6em -0.3em oklch(var(--lch-ink-darkest) / 0.05)
;
display: grid;
height: 1.5em;
inline-size: 1.5em;
padding: 0.325em 0.275em 0.225em 0.275em;
place-content: center;
width: 1.5em;
}
svg {
height: 100%;
margin-inline-start: 0.4125em;
margin-inline-end: 0.5375em;
max-height: 0.8625em;
overflow: visible;
width: auto;
}
}
/* Dialog
/* ------------------------------------------------------------------------ */
.nav__menu.popup {
--panel-border-color: var(--color-selected-dark);
--panel-border-radius: 1.4em;
--panel-padding: var(--block-space);
--panel-size: 45ch;
--popup-display: grid;
--nav-section-gap: 2px;
block-size: auto;
box-shadow: 0 0 0 1px oklch(var(--lch-blue-medium) / 5%),
0 0.2em 0.2em oklch(var(--lch-blue-medium) / 5%),
0 0.4em 0.4em oklch(var(--lch-blue-medium) / 5%),
0 0.8em 0.8em oklch(var(--lch-blue-medium) / 5%);
gap: var(--nav-section-gap);
grid-template-rows: auto 1fr auto;
inset: 0 0 auto 0;
margin-inline: auto;
max-block-size: calc(100dvh - var(--block-space) - var(--footer-height));
overflow: hidden;
padding-block-end: 0;
scrollbar-gutter: stable both-edges;
transform-origin: top center;
translate: 0;
z-index: var(--z-nav);
@media (max-height: 668px) {
max-block-size: calc(100dvh - var(--block-space));
}
}
.nav__scroll-container {
display: flex;
flex-direction: column;
gap: var(--nav-section-gap);
margin-inline: calc(-1 * var(--block-space)); /* space for scrollbar */
overflow: auto;
padding-block-end: var(--nav-section-gap);
padding-inline: var(--block-space);
}
.nav__close {
@media (any-hover: hover) {
display: none !important;
}
}
.nav__section {
border-block-start: 1px solid var(--color-ink-lighter);
font-size: var(--text-small);
&[open] {
padding-block-end: 0.4rem;
}
}
.nav__section--secret:not([data-is-filtering]) {
display: none;
}
.nav__header {
--actions-width: 2rem;
display: grid;
grid-template-columns: var(--actions-width) 1fr var(--actions-width);
grid-template-areas:
"actions-start title actions-end";
justify-items: center;
}
.nav__header-actions {
display: flex;
font-size: var(--text-x-small);
gap: var(--inline-space);
inline-size: var(--actions-width);
min-inline-size: fit-content;
}
.nav__header-actions--end {
grid-area: actions-end;
justify-content: flex-end;
margin-inline-start: auto;
}
.nav__header-actions--start {
grid-area: actions-start;
margin-inline-end: auto;
}
.nav__header-title {
color: inherit;
grid-area: title;
justify-content: center;
margin: auto;
min-inline-size: 0;
text-align: center;
}
.nav__hotkeys {
--gap: 8px;
align-items: center;
display: flex;
flex-wrap: wrap;
gap: var(--gap);
inline-size: 100%;
list-style: none;
justify-content: center;
margin: var(--block-space) auto calc(var(--block-space-half) / 2);
max-inline-size: 100%;
/* When all its children are hidden, hide this as well so it doesn't take up space */
&:has(.popup__item[hidden]):not(:has(.popup__item:not([hidden]))) {
display: none;
}
.btn {
--btn-border-radius: 0.4em;
align-content: end;
aspect-ratio: 5/3;
background-color: var(--color-ink-lightest);
border-radius: 0.5em;
container-type: inline-size;
flex-basis: calc((100% - var(--gap) * 2) / 3);
f
gitextract_3xz67y80/
├── .claude/
│ └── CLAUDE.md
├── .dockerignore
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── config.yml
│ │ └── preapproved.md
│ ├── dependabot.yml
│ └── workflows/
│ ├── ci-checks.yml
│ ├── ci-oss.yml
│ ├── ci-saas.yml
│ ├── dependabot-sync-saas-lockfile.yml
│ ├── publish-image.yml
│ └── test.yml
├── .gitignore
├── .gitleaks.toml
├── .gitleaksignore
├── .mise.toml
├── .rubocop.yml
├── .ruby-version
├── AGENTS.md
├── CONTRIBUTING.md
├── Dockerfile
├── Gemfile
├── Gemfile.saas
├── LICENSE.md
├── README.md
├── Rakefile
├── STYLE.md
├── app/
│ ├── assets/
│ │ ├── images/
│ │ │ └── .keep
│ │ └── stylesheets/
│ │ ├── _global.css
│ │ ├── access-tokens.css
│ │ ├── android.css
│ │ ├── animation.css
│ │ ├── attachments.css
│ │ ├── autoresize.css
│ │ ├── avatars.css
│ │ ├── bar.css
│ │ ├── base.css
│ │ ├── blank-slates.css
│ │ ├── bubble.css
│ │ ├── buttons.css
│ │ ├── card-columns.css
│ │ ├── card-perma.css
│ │ ├── cards.css
│ │ ├── circled-text.css
│ │ ├── color-picker.css
│ │ ├── comments.css
│ │ ├── credentials.css
│ │ ├── dialog.css
│ │ ├── dividers.css
│ │ ├── drag_and_drop.css
│ │ ├── events.css
│ │ ├── expandable.css
│ │ ├── filters.css
│ │ ├── flash.css
│ │ ├── font-face.css
│ │ ├── golden-effect.css
│ │ ├── header.css
│ │ ├── icons.css
│ │ ├── import.css
│ │ ├── inputs.css
│ │ ├── ios.css
│ │ ├── knobs.css
│ │ ├── layout.css
│ │ ├── lexxy.css
│ │ ├── lightbox.css
│ │ ├── markdown.css
│ │ ├── native.css
│ │ ├── nav.css
│ │ ├── notifications.css
│ │ ├── pagination.css
│ │ ├── panels.css
│ │ ├── performance-notice.css
│ │ ├── pins.css
│ │ ├── popup.css
│ │ ├── print.css
│ │ ├── pwa.css
│ │ ├── qr-codes.css
│ │ ├── reactions.css
│ │ ├── reset.css
│ │ ├── search.css
│ │ ├── separators.css
│ │ ├── settings.css
│ │ ├── spinners.css
│ │ ├── steps.css
│ │ ├── syntax.css
│ │ ├── theme-switcher.css
│ │ ├── toggles.css
│ │ ├── tooltips.css
│ │ ├── trays.css
│ │ ├── user.css
│ │ ├── utilities.css
│ │ └── welcome-letter.css
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection.rb
│ ├── controllers/
│ │ ├── account/
│ │ │ ├── cancellations_controller.rb
│ │ │ ├── entropies_controller.rb
│ │ │ ├── exports_controller.rb
│ │ │ ├── imports_controller.rb
│ │ │ ├── join_codes_controller.rb
│ │ │ └── settings_controller.rb
│ │ ├── admin_controller.rb
│ │ ├── application_controller.rb
│ │ ├── boards/
│ │ │ ├── columns/
│ │ │ │ ├── closeds_controller.rb
│ │ │ │ ├── not_nows_controller.rb
│ │ │ │ └── streams_controller.rb
│ │ │ ├── columns_controller.rb
│ │ │ ├── entropies_controller.rb
│ │ │ ├── involvements_controller.rb
│ │ │ └── publications_controller.rb
│ │ ├── boards_controller.rb
│ │ ├── cards/
│ │ │ ├── assignments_controller.rb
│ │ │ ├── boards_controller.rb
│ │ │ ├── closures_controller.rb
│ │ │ ├── columns_controller.rb
│ │ │ ├── comments/
│ │ │ │ └── reactions_controller.rb
│ │ │ ├── comments_controller.rb
│ │ │ ├── drafts_controller.rb
│ │ │ ├── goldnesses_controller.rb
│ │ │ ├── images_controller.rb
│ │ │ ├── not_nows_controller.rb
│ │ │ ├── pins_controller.rb
│ │ │ ├── previews_controller.rb
│ │ │ ├── publishes_controller.rb
│ │ │ ├── reactions_controller.rb
│ │ │ ├── readings_controller.rb
│ │ │ ├── self_assignments_controller.rb
│ │ │ ├── steps_controller.rb
│ │ │ ├── taggings_controller.rb
│ │ │ ├── triages_controller.rb
│ │ │ └── watches_controller.rb
│ │ ├── cards_controller.rb
│ │ ├── client_configurations_controller.rb
│ │ ├── columns/
│ │ │ ├── cards/
│ │ │ │ └── drops/
│ │ │ │ ├── closures_controller.rb
│ │ │ │ ├── columns_controller.rb
│ │ │ │ ├── not_nows_controller.rb
│ │ │ │ └── streams_controller.rb
│ │ │ ├── left_positions_controller.rb
│ │ │ └── right_positions_controller.rb
│ │ ├── concerns/
│ │ │ ├── authentication/
│ │ │ │ └── via_magic_link.rb
│ │ │ ├── authentication.rb
│ │ │ ├── authorization.rb
│ │ │ ├── block_search_engine_indexing.rb
│ │ │ ├── board_scoped.rb
│ │ │ ├── card_scoped.rb
│ │ │ ├── column_scoped.rb
│ │ │ ├── current_request.rb
│ │ │ ├── current_timezone.rb
│ │ │ ├── day_timelines_scoped.rb
│ │ │ ├── filter_scoped.rb
│ │ │ ├── request_forgery_protection.rb
│ │ │ ├── routing_headers.rb
│ │ │ ├── set_platform.rb
│ │ │ ├── turbo_flash.rb
│ │ │ └── view_transitions.rb
│ │ ├── events/
│ │ │ ├── day_timeline/
│ │ │ │ └── columns_controller.rb
│ │ │ └── days_controller.rb
│ │ ├── events_controller.rb
│ │ ├── filters/
│ │ │ └── settings_refreshes_controller.rb
│ │ ├── filters_controller.rb
│ │ ├── join_codes_controller.rb
│ │ ├── landings_controller.rb
│ │ ├── my/
│ │ │ ├── access_tokens_controller.rb
│ │ │ ├── identities_controller.rb
│ │ │ ├── menus_controller.rb
│ │ │ ├── passkey_challenges_controller.rb
│ │ │ ├── passkeys_controller.rb
│ │ │ ├── pins_controller.rb
│ │ │ └── timezones_controller.rb
│ │ ├── notifications/
│ │ │ ├── bulk_readings_controller.rb
│ │ │ ├── readings_controller.rb
│ │ │ ├── settings_controller.rb
│ │ │ ├── trays_controller.rb
│ │ │ └── unsubscribes_controller.rb
│ │ ├── notifications_controller.rb
│ │ ├── prompts/
│ │ │ ├── boards/
│ │ │ │ └── users_controller.rb
│ │ │ ├── cards_controller.rb
│ │ │ ├── tags_controller.rb
│ │ │ └── users_controller.rb
│ │ ├── public/
│ │ │ ├── base_controller.rb
│ │ │ ├── boards/
│ │ │ │ ├── columns/
│ │ │ │ │ ├── closeds_controller.rb
│ │ │ │ │ ├── not_nows_controller.rb
│ │ │ │ │ └── streams_controller.rb
│ │ │ │ └── columns_controller.rb
│ │ │ ├── boards_controller.rb
│ │ │ └── cards_controller.rb
│ │ ├── pwa_controller.rb
│ │ ├── qr_codes_controller.rb
│ │ ├── searches/
│ │ │ └── queries_controller.rb
│ │ ├── searches_controller.rb
│ │ ├── sessions/
│ │ │ ├── magic_links_controller.rb
│ │ │ ├── menus_controller.rb
│ │ │ ├── passkeys_controller.rb
│ │ │ └── transfers_controller.rb
│ │ ├── sessions_controller.rb
│ │ ├── signups/
│ │ │ └── completions_controller.rb
│ │ ├── signups_controller.rb
│ │ ├── tags_controller.rb
│ │ ├── users/
│ │ │ ├── avatars_controller.rb
│ │ │ ├── data_exports_controller.rb
│ │ │ ├── email_addresses/
│ │ │ │ └── confirmations_controller.rb
│ │ │ ├── email_addresses_controller.rb
│ │ │ ├── events_controller.rb
│ │ │ ├── joins_controller.rb
│ │ │ ├── push_subscriptions_controller.rb
│ │ │ ├── roles_controller.rb
│ │ │ └── verifications_controller.rb
│ │ ├── users_controller.rb
│ │ ├── webhooks/
│ │ │ └── activations_controller.rb
│ │ └── webhooks_controller.rb
│ ├── helpers/
│ │ ├── accesses_helper.rb
│ │ ├── application_helper.rb
│ │ ├── avatars_helper.rb
│ │ ├── boards_helper.rb
│ │ ├── bridge_helper.rb
│ │ ├── cards_helper.rb
│ │ ├── clipboard_helper.rb
│ │ ├── columns_helper.rb
│ │ ├── comments_helper.rb
│ │ ├── emoji_helper.rb
│ │ ├── entropy_helper.rb
│ │ ├── events_helper.rb
│ │ ├── excerpt_helper.rb
│ │ ├── filters_helper.rb
│ │ ├── forms_helper.rb
│ │ ├── hotkeys_helper.rb
│ │ ├── html_helper.rb
│ │ ├── login_helper.rb
│ │ ├── messages_helper.rb
│ │ ├── my/
│ │ │ └── menu_helper.rb
│ │ ├── notifications_helper.rb
│ │ ├── pagination_helper.rb
│ │ ├── qr_codes_helper.rb
│ │ ├── reactions_helper.rb
│ │ ├── rich_text_helper.rb
│ │ ├── tenanting_helper.rb
│ │ ├── time_helper.rb
│ │ ├── users_helper.rb
│ │ └── webhooks_helper.rb
│ ├── javascript/
│ │ ├── application.js
│ │ ├── controllers/
│ │ │ ├── application.js
│ │ │ ├── assignment_limit_controller.js
│ │ │ ├── auto_click_controller.js
│ │ │ ├── auto_save_controller.js
│ │ │ ├── auto_submit_controller.js
│ │ │ ├── autoresize_controller.js
│ │ │ ├── badge_controller.js
│ │ │ ├── bar_controller.js
│ │ │ ├── beacon_controller.js
│ │ │ ├── boards_form_controller.js
│ │ │ ├── bridge/
│ │ │ │ ├── buttons_controller.js
│ │ │ │ ├── form_controller.js
│ │ │ │ ├── insets_controller.js
│ │ │ │ ├── overflow_menu_controller.js
│ │ │ │ ├── share_controller.js
│ │ │ │ ├── stamp_controller.js
│ │ │ │ ├── text_size_controller.js
│ │ │ │ └── title_controller.js
│ │ │ ├── bubble_controller.js
│ │ │ ├── card_hotkeys_controller.js
│ │ │ ├── clear_offline_cache_controller.js
│ │ │ ├── clicker_controller.js
│ │ │ ├── collapsible_columns_controller.js
│ │ │ ├── combobox_controller.js
│ │ │ ├── copy_to_clipboard_controller.js
│ │ │ ├── css_variable_counter_controller.js
│ │ │ ├── details_controller.js
│ │ │ ├── dialog_controller.js
│ │ │ ├── dialog_manager_controller.js
│ │ │ ├── drag_and_drop_controller.js
│ │ │ ├── drag_and_strum_controller.js
│ │ │ ├── element_removal_controller.js
│ │ │ ├── expandable_on_native_controller.js
│ │ │ ├── fetch_on_visible_controller.js
│ │ │ ├── filter_controller.js
│ │ │ ├── filter_form_controller.js
│ │ │ ├── filter_settings_controller.js
│ │ │ ├── form_controller.js
│ │ │ ├── frame_controller.js
│ │ │ ├── frame_reloader_controller.js
│ │ │ ├── hotkey_controller.js
│ │ │ ├── index.js
│ │ │ ├── knob_controller.js
│ │ │ ├── lightbox_controller.js
│ │ │ ├── local_save_controller.js
│ │ │ ├── local_time_controller.js
│ │ │ ├── magic_link_controller.js
│ │ │ ├── multi_selection_combobox_controller.js
│ │ │ ├── nav_section_expander_controller.js
│ │ │ ├── navigable_list_controller.js
│ │ │ ├── notifications_controller.js
│ │ │ ├── outlet_auto_save_controller.js
│ │ │ ├── pagination_controller.js
│ │ │ ├── reaction_delete_controller.js
│ │ │ ├── reaction_emoji_controller.js
│ │ │ ├── related_element_controller.js
│ │ │ ├── retarget_links_controller.js
│ │ │ ├── scroll_to_controller.js
│ │ │ ├── search_form_controller.js
│ │ │ ├── soft_keyboard_controller.js
│ │ │ ├── syntax_highlight_controller.js
│ │ │ ├── theme_controller.js
│ │ │ ├── timezone_cookie_controller.js
│ │ │ ├── toggle_class_controller.js
│ │ │ ├── toggle_enable_controller.js
│ │ │ ├── tooltip_controller.js
│ │ │ ├── touch_placeholder_controller.js
│ │ │ ├── turbo_navigation_controller.js
│ │ │ └── upload_preview_controller.js
│ │ ├── helpers/
│ │ │ ├── bridge/
│ │ │ │ └── viewport_helpers.js
│ │ │ ├── date_helpers.js
│ │ │ ├── form_helpers.js
│ │ │ ├── html_helpers.js
│ │ │ ├── orientation_helpers.js
│ │ │ ├── platform_helpers.js
│ │ │ ├── scroll_helpers.js
│ │ │ ├── text_helpers.js
│ │ │ └── timing_helpers.js
│ │ ├── initializers/
│ │ │ ├── bridge/
│ │ │ │ └── bridge_element.js
│ │ │ ├── current.js
│ │ │ ├── index.js
│ │ │ ├── lexxy_markdown_paste.js
│ │ │ └── offline.js
│ │ └── lib/
│ │ └── action_pack/
│ │ ├── passkey.js
│ │ └── webauthn.js
│ ├── jobs/
│ │ ├── account/
│ │ │ ├── data_import_job.rb
│ │ │ └── incinerate_due_job.rb
│ │ ├── application_job.rb
│ │ ├── board/
│ │ │ └── clean_inaccessible_data_job.rb
│ │ ├── card/
│ │ │ ├── activity_spike/
│ │ │ │ └── detection_job.rb
│ │ │ ├── clean_inaccessible_data_job.rb
│ │ │ └── remove_inaccessible_notifications_job.rb
│ │ ├── concerns/
│ │ │ └── smtp_delivery_error_handling.rb
│ │ ├── data_export_job.rb
│ │ ├── delete_unused_tags_job.rb
│ │ ├── event/
│ │ │ └── webhook_dispatch_job.rb
│ │ ├── mention/
│ │ │ └── create_job.rb
│ │ ├── notification/
│ │ │ ├── bundle/
│ │ │ │ ├── deliver_all_job.rb
│ │ │ │ └── deliver_job.rb
│ │ │ └── push_job.rb
│ │ ├── notify_recipients_job.rb
│ │ ├── push_notification_job.rb
│ │ ├── storage/
│ │ │ ├── materialize_job.rb
│ │ │ └── reconcile_job.rb
│ │ └── webhook/
│ │ └── delivery_job.rb
│ ├── mailers/
│ │ ├── account_mailer.rb
│ │ ├── application_mailer.rb
│ │ ├── concerns/
│ │ │ └── mailers/
│ │ │ └── unsubscribable.rb
│ │ ├── export_mailer.rb
│ │ ├── import_mailer.rb
│ │ ├── magic_link_mailer.rb
│ │ ├── notification/
│ │ │ └── bundle_mailer.rb
│ │ └── user_mailer.rb
│ ├── models/
│ │ ├── access.rb
│ │ ├── account/
│ │ │ ├── cancellable.rb
│ │ │ ├── cancellation.rb
│ │ │ ├── data_transfer/
│ │ │ │ ├── account_record_set.rb
│ │ │ │ ├── action_text/
│ │ │ │ │ └── rich_text_record_set.rb
│ │ │ │ ├── active_storage/
│ │ │ │ │ ├── attachment_record_set.rb
│ │ │ │ │ ├── blob_record_set.rb
│ │ │ │ │ └── file_record_set.rb
│ │ │ │ ├── entropy_record_set.rb
│ │ │ │ ├── manifest.rb
│ │ │ │ ├── record_set.rb
│ │ │ │ └── user_record_set.rb
│ │ │ ├── entropic.rb
│ │ │ ├── export.rb
│ │ │ ├── external_id_sequence.rb
│ │ │ ├── import.rb
│ │ │ ├── incineratable.rb
│ │ │ ├── join_code.rb
│ │ │ ├── multi_tenantable.rb
│ │ │ ├── seedeable.rb
│ │ │ ├── seeder.rb
│ │ │ └── storage.rb
│ │ ├── account.rb
│ │ ├── admin.rb
│ │ ├── application_platform.rb
│ │ ├── application_record.rb
│ │ ├── assignment.rb
│ │ ├── board/
│ │ │ ├── accessible.rb
│ │ │ ├── auto_postponing.rb
│ │ │ ├── broadcastable.rb
│ │ │ ├── cards.rb
│ │ │ ├── entropic.rb
│ │ │ ├── publication.rb
│ │ │ ├── publishable.rb
│ │ │ ├── storage.rb
│ │ │ └── triageable.rb
│ │ ├── board.rb
│ │ ├── card/
│ │ │ ├── accessible.rb
│ │ │ ├── activity_spike/
│ │ │ │ └── detector.rb
│ │ │ ├── activity_spike.rb
│ │ │ ├── assignable.rb
│ │ │ ├── broadcastable.rb
│ │ │ ├── closeable.rb
│ │ │ ├── colored.rb
│ │ │ ├── commentable.rb
│ │ │ ├── entropic.rb
│ │ │ ├── entropy.rb
│ │ │ ├── eventable/
│ │ │ │ └── system_commenter.rb
│ │ │ ├── eventable.rb
│ │ │ ├── exportable.rb
│ │ │ ├── golden.rb
│ │ │ ├── goldness.rb
│ │ │ ├── mentions.rb
│ │ │ ├── multistep.rb
│ │ │ ├── not_now.rb
│ │ │ ├── pinnable.rb
│ │ │ ├── postponable.rb
│ │ │ ├── promptable.rb
│ │ │ ├── readable.rb
│ │ │ ├── searchable.rb
│ │ │ ├── stallable.rb
│ │ │ ├── statuses.rb
│ │ │ ├── taggable.rb
│ │ │ ├── triageable.rb
│ │ │ └── watchable.rb
│ │ ├── card.rb
│ │ ├── closure.rb
│ │ ├── color.rb
│ │ ├── column/
│ │ │ ├── colored.rb
│ │ │ └── positioned.rb
│ │ ├── column.rb
│ │ ├── comment/
│ │ │ ├── eventable.rb
│ │ │ ├── mentions.rb
│ │ │ ├── promptable.rb
│ │ │ └── searchable.rb
│ │ ├── comment.rb
│ │ ├── concerns/
│ │ │ ├── attachments.rb
│ │ │ ├── eventable.rb
│ │ │ ├── filterable.rb
│ │ │ ├── mentions.rb
│ │ │ ├── notifiable.rb
│ │ │ ├── searchable.rb
│ │ │ └── storage/
│ │ │ ├── totaled.rb
│ │ │ └── tracked.rb
│ │ ├── current.rb
│ │ ├── entropy.rb
│ │ ├── event/
│ │ │ ├── description.rb
│ │ │ ├── particulars.rb
│ │ │ └── promptable.rb
│ │ ├── event.rb
│ │ ├── export.rb
│ │ ├── filter/
│ │ │ ├── fields.rb
│ │ │ ├── params.rb
│ │ │ ├── resources.rb
│ │ │ └── summarized.rb
│ │ ├── filter.rb
│ │ ├── identity/
│ │ │ ├── access_token.rb
│ │ │ ├── joinable.rb
│ │ │ └── transferable.rb
│ │ ├── identity.rb
│ │ ├── magic_link/
│ │ │ └── code.rb
│ │ ├── magic_link.rb
│ │ ├── mention.rb
│ │ ├── notification/
│ │ │ ├── bundle.rb
│ │ │ ├── default_payload.rb
│ │ │ ├── event_payload.rb
│ │ │ ├── mention_payload.rb
│ │ │ ├── push_target/
│ │ │ │ └── web.rb
│ │ │ ├── push_target.rb
│ │ │ └── pushable.rb
│ │ ├── notification.rb
│ │ ├── notifier/
│ │ │ ├── card_event_notifier.rb
│ │ │ ├── comment_event_notifier.rb
│ │ │ └── mention_notifier.rb
│ │ ├── notifier.rb
│ │ ├── passkey/
│ │ │ └── authenticator.rb
│ │ ├── pin.rb
│ │ ├── push/
│ │ │ └── subscription.rb
│ │ ├── push.rb
│ │ ├── qr_code_link.rb
│ │ ├── reaction.rb
│ │ ├── search/
│ │ │ ├── highlighter.rb
│ │ │ ├── query.rb
│ │ │ ├── record/
│ │ │ │ ├── sqlite/
│ │ │ │ │ └── fts.rb
│ │ │ │ ├── sqlite.rb
│ │ │ │ └── trilogy.rb
│ │ │ ├── record.rb
│ │ │ ├── result.rb
│ │ │ └── stemmer.rb
│ │ ├── search.rb
│ │ ├── session.rb
│ │ ├── signup/
│ │ │ └── account_name_generator.rb
│ │ ├── signup.rb
│ │ ├── ssrf_protection.rb
│ │ ├── step.rb
│ │ ├── storage/
│ │ │ ├── attachment_tracking.rb
│ │ │ ├── entry.rb
│ │ │ └── total.rb
│ │ ├── storage.rb
│ │ ├── tag/
│ │ │ └── attachable.rb
│ │ ├── tag.rb
│ │ ├── tagging.rb
│ │ ├── time_window_parser.rb
│ │ ├── user/
│ │ │ ├── accessor.rb
│ │ │ ├── assignee.rb
│ │ │ ├── attachable.rb
│ │ │ ├── avatar.rb
│ │ │ ├── configurable.rb
│ │ │ ├── data_export.rb
│ │ │ ├── day_timeline/
│ │ │ │ ├── column.rb
│ │ │ │ └── serializable.rb
│ │ │ ├── day_timeline.rb
│ │ │ ├── email_address_changeable.rb
│ │ │ ├── filtering.rb
│ │ │ ├── mentionable.rb
│ │ │ ├── named.rb
│ │ │ ├── notifiable.rb
│ │ │ ├── role.rb
│ │ │ ├── searcher.rb
│ │ │ ├── settings.rb
│ │ │ ├── timelined.rb
│ │ │ ├── transferable.rb
│ │ │ └── watcher.rb
│ │ ├── user.rb
│ │ ├── watch.rb
│ │ ├── webhook/
│ │ │ ├── delinquency_tracker.rb
│ │ │ ├── delivery.rb
│ │ │ └── triggerable.rb
│ │ ├── webhook.rb
│ │ ├── zip_file/
│ │ │ ├── reader/
│ │ │ │ └── io.rb
│ │ │ ├── reader.rb
│ │ │ ├── remote_io.rb
│ │ │ └── writer.rb
│ │ └── zip_file.rb
│ └── views/
│ ├── account/
│ │ ├── exports/
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ ├── imports/
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ │ ├── join_codes/
│ │ │ ├── edit.html.erb
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ └── settings/
│ │ ├── _cancellation.html.erb
│ │ ├── _entropy.html.erb
│ │ ├── _export.html.erb
│ │ ├── _name.html.erb
│ │ ├── _user.html.erb
│ │ ├── _users.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jbuilder
│ ├── action_text/
│ │ └── attachables/
│ │ ├── _remote_image.html.erb
│ │ └── _remote_video.html.erb
│ ├── active_storage/
│ │ └── blobs/
│ │ ├── _blob.html.erb
│ │ └── web/
│ │ └── _representation.html.erb
│ ├── bar/
│ │ └── _bar.html.erb
│ ├── boards/
│ │ ├── _access_toggle.html.erb
│ │ ├── _board.json.jbuilder
│ │ ├── columns/
│ │ │ ├── _empty_placeholder.html.erb
│ │ │ ├── closeds/
│ │ │ │ ├── show.html.erb
│ │ │ │ └── show.json.jbuilder
│ │ │ ├── create.turbo_stream.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── not_nows/
│ │ │ │ ├── show.html.erb
│ │ │ │ └── show.json.jbuilder
│ │ │ ├── show.html.erb
│ │ │ ├── show.json.jbuilder
│ │ │ ├── streams/
│ │ │ │ ├── show.html.erb
│ │ │ │ └── show.json.jbuilder
│ │ │ └── update.turbo_stream.erb
│ │ ├── edit/
│ │ │ ├── _auto_close.html.erb
│ │ │ ├── _delete.html.erb
│ │ │ ├── _name.html.erb
│ │ │ ├── _publication.html.erb
│ │ │ └── _users.html.erb
│ │ ├── edit.html.erb
│ │ ├── entropies/
│ │ │ └── update.turbo_stream.erb
│ │ ├── index.json.jbuilder
│ │ ├── involvements/
│ │ │ └── update.html.erb
│ │ ├── new.html.erb
│ │ ├── publications/
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── destroy.turbo_stream.erb
│ │ ├── show/
│ │ │ ├── _closed.html.erb
│ │ │ ├── _column.html.erb
│ │ │ ├── _columns.html.erb
│ │ │ ├── _expander.html.erb
│ │ │ ├── _filtered_cards.html.erb
│ │ │ ├── _not_now.html.erb
│ │ │ ├── _stream.html.erb
│ │ │ └── menu/
│ │ │ ├── _column.html.erb
│ │ │ ├── _column_form.html.erb
│ │ │ ├── _columns.html.erb
│ │ │ └── _maximize.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jbuilder
│ ├── cards/
│ │ ├── _broadcasts.html.erb
│ │ ├── _card.json.jbuilder
│ │ ├── _container.html.erb
│ │ ├── _delete.html.erb
│ │ ├── _messages.html.erb
│ │ ├── assignments/
│ │ │ ├── _user.html.erb
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── new.html.erb
│ │ ├── boards/
│ │ │ └── edit.html.erb
│ │ ├── closures/
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── destroy.turbo_stream.erb
│ │ ├── columns/
│ │ │ ├── _column.html.erb
│ │ │ └── edit.html.erb
│ │ ├── comments/
│ │ │ ├── _comment.html.erb
│ │ │ ├── _comment.json.jbuilder
│ │ │ ├── _new.html.erb
│ │ │ ├── _watchers.html.erb
│ │ │ ├── create.turbo_stream.erb
│ │ │ ├── destroy.turbo_stream.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── show.html.erb
│ │ │ ├── show.json.jbuilder
│ │ │ └── update.turbo_stream.erb
│ │ ├── container/
│ │ │ ├── _closure.html.erb
│ │ │ ├── _closure_buttons.html.erb
│ │ │ ├── _content.html.erb
│ │ │ ├── _content_display.html.erb
│ │ │ ├── _gild.html.erb
│ │ │ ├── _image.html.erb
│ │ │ ├── _save_button.html.erb
│ │ │ └── footer/
│ │ │ ├── _create.html.erb
│ │ │ └── _published.html.erb
│ │ ├── display/
│ │ │ ├── _preview.html.erb
│ │ │ ├── _previews.html.erb
│ │ │ ├── _public_preview.html.erb
│ │ │ ├── _public_previews.html.erb
│ │ │ ├── common/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _background.html.erb
│ │ │ │ ├── _board.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ └── _stamp.html.erb
│ │ │ ├── mini/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ └── _tags.html.erb
│ │ │ ├── perma/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _background.html.erb
│ │ │ │ ├── _board.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ ├── _steps.html.erb
│ │ │ │ └── _tags.html.erb
│ │ │ ├── preview/
│ │ │ │ ├── _assignees.html.erb
│ │ │ │ ├── _board.html.erb
│ │ │ │ ├── _boosts.html.erb
│ │ │ │ ├── _bubble.html.erb
│ │ │ │ ├── _columns.html.erb
│ │ │ │ ├── _comments.html.erb
│ │ │ │ ├── _meta.html.erb
│ │ │ │ ├── _people.html.erb
│ │ │ │ ├── _steps.html.erb
│ │ │ │ └── _tags.html.erb
│ │ │ └── public_preview/
│ │ │ ├── _columns.html.erb
│ │ │ └── _meta.html.erb
│ │ ├── drafts/
│ │ │ ├── _container.html.erb
│ │ │ └── show.html.erb
│ │ ├── edit.html.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── not_nows/
│ │ │ └── create.turbo_stream.erb
│ │ ├── pins/
│ │ │ ├── _pin_button.html.erb
│ │ │ └── show.html.erb
│ │ ├── previews/
│ │ │ └── index.turbo_stream.erb
│ │ ├── readings/
│ │ │ └── create.turbo_stream.erb
│ │ ├── show.html.erb
│ │ ├── show.json.jbuilder
│ │ ├── steps/
│ │ │ ├── _step.html.erb
│ │ │ ├── _step.json.jbuilder
│ │ │ ├── create.turbo_stream.erb
│ │ │ ├── destroy.turbo_stream.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── show.html.erb
│ │ │ ├── show.json.jbuilder
│ │ │ └── update.turbo_stream.erb
│ │ ├── taggings/
│ │ │ ├── _tag.html.erb
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── new.html.erb
│ │ ├── triage/
│ │ │ └── _columns.html.erb
│ │ ├── update.turbo_stream.erb
│ │ └── watches/
│ │ ├── _refresh.turbo_stream.erb
│ │ ├── _watch_button.html.erb
│ │ ├── create.turbo_stream.erb
│ │ ├── destroy.turbo_stream.erb
│ │ └── show.html.erb
│ ├── client_configurations/
│ │ ├── android_v1.json
│ │ └── ios_v1.json
│ ├── columns/
│ │ ├── _column.json.jbuilder
│ │ ├── _refresh_adjacent_columns.turbo_stream.erb
│ │ ├── cards/
│ │ │ └── drops/
│ │ │ ├── closures/
│ │ │ │ └── create.turbo_stream.erb
│ │ │ ├── columns/
│ │ │ │ └── create.turbo_stream.erb
│ │ │ ├── not_nows/
│ │ │ │ └── create.turbo_stream.erb
│ │ │ └── streams/
│ │ │ └── create.turbo_stream.erb
│ │ ├── left_positions/
│ │ │ └── create.turbo_stream.erb
│ │ ├── right_positions/
│ │ │ └── create.turbo_stream.erb
│ │ └── show/
│ │ └── _add_card_button.html.erb
│ ├── entropy/
│ │ ├── _auto_close.html.erb
│ │ └── _knob.html.erb
│ ├── event_summaries/
│ │ └── _event_summary.html.erb
│ ├── events/
│ │ ├── _day.html.erb
│ │ ├── _empty_days.html.erb
│ │ ├── _event.html.erb
│ │ ├── day_timeline/
│ │ │ ├── _column.html.erb
│ │ │ ├── _columns.html.erb
│ │ │ └── columns/
│ │ │ ├── _events.html.erb
│ │ │ └── show.html.erb
│ │ ├── days/
│ │ │ └── index.html.erb
│ │ ├── event/
│ │ │ ├── _attachments.html.erb
│ │ │ ├── _layout.html.erb
│ │ │ ├── attachments/
│ │ │ │ ├── _attachment.html.erb
│ │ │ │ ├── _remote_image.html.erb
│ │ │ │ └── _remote_video.html.erb
│ │ │ └── eventable/
│ │ │ ├── _card.html.erb
│ │ │ ├── _card_published.html.erb
│ │ │ └── _comment.html.erb
│ │ ├── index/
│ │ │ ├── _add_board_button.html.erb
│ │ │ ├── _add_card_button.html.erb
│ │ │ ├── _filter.html.erb
│ │ │ └── filter/
│ │ │ ├── _board.html.erb
│ │ │ └── _user.html.erb
│ │ └── index.html.erb
│ ├── filters/
│ │ ├── _filter_toggle.html.erb
│ │ ├── _settings.html.erb
│ │ ├── create.turbo_stream.erb
│ │ ├── destroy.turbo_stream.erb
│ │ ├── settings/
│ │ │ ├── _assignees.html.erb
│ │ │ ├── _boards.html.erb
│ │ │ ├── _cards.html.erb
│ │ │ ├── _closers.html.erb
│ │ │ ├── _controls.html.erb
│ │ │ ├── _creators.html.erb
│ │ │ ├── _indexed_by.html.erb
│ │ │ ├── _manage.html.erb
│ │ │ ├── _sorted_by.html.erb
│ │ │ ├── _tags.html.erb
│ │ │ ├── _terms.html.erb
│ │ │ ├── _time_window.html.erb
│ │ │ └── _toggle.html.erb
│ │ └── settings_refreshes/
│ │ └── create.turbo_stream.erb
│ ├── join_codes/
│ │ ├── inactive.html.erb
│ │ └── new.html.erb
│ ├── layouts/
│ │ ├── _lightbox.html.erb
│ │ ├── _theme_preference.html.erb
│ │ ├── action_text/
│ │ │ └── contents/
│ │ │ └── _content.html.erb
│ │ ├── application.html.erb
│ │ ├── mailer.html.erb
│ │ ├── mailer.text.erb
│ │ ├── public.html.erb
│ │ └── shared/
│ │ ├── _colophon.html.erb
│ │ ├── _flash.html.erb
│ │ ├── _head.html.erb
│ │ ├── _time_zone.html.erb
│ │ ├── _user_css.html.erb
│ │ └── _welcome_letter.html.erb
│ ├── mailers/
│ │ ├── account_mailer/
│ │ │ ├── cancellation.html.erb
│ │ │ └── cancellation.text.erb
│ │ ├── export_mailer/
│ │ │ ├── completed.html.erb
│ │ │ └── completed.text.erb
│ │ ├── identity_mailer/
│ │ │ └── email_change_confirmation.text.erb
│ │ ├── import_mailer/
│ │ │ ├── completed.html.erb
│ │ │ ├── completed.text.erb
│ │ │ ├── failed.html.erb
│ │ │ └── failed.text.erb
│ │ ├── magic_link_mailer/
│ │ │ ├── sign_in_instructions.html.erb
│ │ │ └── sign_in_instructions.text.erb
│ │ └── notification/
│ │ └── bundle_mailer/
│ │ ├── _notification.html.erb
│ │ ├── _notification.text.erb
│ │ ├── event/
│ │ │ ├── _body.html.erb
│ │ │ └── _body.text.erb
│ │ ├── mention/
│ │ │ ├── _body.html.erb
│ │ │ └── _body.text.erb
│ │ ├── notification.html.erb
│ │ └── notification.text.erb
│ ├── my/
│ │ ├── _menu.html.erb
│ │ ├── access_tokens/
│ │ │ ├── _access_token.html.erb
│ │ │ ├── _access_token.json.jbuilder
│ │ │ ├── index.html.erb
│ │ │ ├── index.json.jbuilder
│ │ │ ├── new.html.erb
│ │ │ └── show.html.erb
│ │ ├── identities/
│ │ │ ├── _account.json.jbuilder
│ │ │ └── show.json.jbuilder
│ │ ├── menus/
│ │ │ ├── _accounts.html.erb
│ │ │ ├── _boards.html.erb
│ │ │ ├── _custom_views.html.erb
│ │ │ ├── _jump.html.erb
│ │ │ ├── _people.html.erb
│ │ │ ├── _settings.html.erb
│ │ │ ├── _shortcuts.html.erb
│ │ │ ├── _tags.html.erb
│ │ │ └── show.html.erb
│ │ ├── passkeys/
│ │ │ ├── _passkey.html.erb
│ │ │ ├── edit.html.erb
│ │ │ └── index.html.erb
│ │ └── pins/
│ │ ├── _pin.html.erb
│ │ ├── _tray.html.erb
│ │ ├── index.html.erb
│ │ └── index.json.jbuilder
│ ├── notifications/
│ │ ├── _notification.html.erb
│ │ ├── _notification.json.jbuilder
│ │ ├── _tray.html.erb
│ │ ├── index/
│ │ │ ├── _read_notifications.html.erb
│ │ │ └── _unread_notifications.html.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── index.turbo_stream.erb
│ │ ├── notification/
│ │ │ ├── _body.html.erb
│ │ │ ├── _header.html.erb
│ │ │ ├── event/
│ │ │ │ ├── _body.html.erb
│ │ │ │ └── _body.json.jbuilder
│ │ │ └── mention/
│ │ │ ├── _body.html.erb
│ │ │ └── _body.json.jbuilder
│ │ ├── readings/
│ │ │ ├── create.turbo_stream.erb
│ │ │ └── destroy.turbo_stream.erb
│ │ ├── settings/
│ │ │ ├── _board.html.erb
│ │ │ ├── _browser.html.erb
│ │ │ ├── _email.html.erb
│ │ │ ├── _install.html.erb
│ │ │ ├── _push_notifications.html.erb
│ │ │ ├── _system.html.erb
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ ├── trays/
│ │ │ ├── show.html.erb
│ │ │ └── show.json.jbuilder
│ │ └── unsubscribes/
│ │ ├── new.html.erb
│ │ └── show.html.erb
│ ├── prompts/
│ │ ├── boards/
│ │ │ └── users/
│ │ │ ├── _user.html.erb
│ │ │ └── index.html.erb
│ │ ├── cards/
│ │ │ ├── _card.html.erb
│ │ │ └── index.html.erb
│ │ ├── commands/
│ │ │ ├── _command.html.erb
│ │ │ └── index.html.erb
│ │ ├── tags/
│ │ │ ├── _tag.html.erb
│ │ │ └── index.html.erb
│ │ └── users/
│ │ └── index.html.erb
│ ├── public/
│ │ ├── _footer.html.erb
│ │ ├── boards/
│ │ │ ├── card_previews/
│ │ │ │ └── index.turbo_stream.erb
│ │ │ ├── columns/
│ │ │ │ ├── closeds/
│ │ │ │ │ └── show.html.erb
│ │ │ │ ├── not_nows/
│ │ │ │ │ └── show.html.erb
│ │ │ │ ├── show.html.erb
│ │ │ │ └── streams/
│ │ │ │ └── show.html.erb
│ │ │ ├── show/
│ │ │ │ ├── _closed.html.erb
│ │ │ │ ├── _column.html.erb
│ │ │ │ ├── _columns.html.erb
│ │ │ │ ├── _not_now.html.erb
│ │ │ │ └── _stream.html.erb
│ │ │ └── show.html.erb
│ │ └── cards/
│ │ ├── show/
│ │ │ ├── _content.html.erb
│ │ │ └── _steps.html.erb
│ │ └── show.html.erb
│ ├── pwa/
│ │ ├── manifest.json.erb
│ │ └── service_worker.js.erb
│ ├── reactions/
│ │ ├── _menu.html.erb
│ │ ├── _reaction.html.erb
│ │ ├── _reaction.json.jbuilder
│ │ ├── _reactions.html.erb
│ │ ├── create.turbo_stream.erb
│ │ ├── destroy.turbo_stream.erb
│ │ ├── index.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── new.html.erb
│ │ └── show.json.jbuilder
│ ├── searches/
│ │ ├── _form.html.erb
│ │ ├── _result.html.erb
│ │ ├── _results.html.erb
│ │ ├── show.html.erb
│ │ └── show.json.jbuilder
│ ├── sessions/
│ │ ├── _footer.html.erb
│ │ ├── magic_links/
│ │ │ └── show.html.erb
│ │ ├── menus/
│ │ │ └── show.html.erb
│ │ ├── new.html.erb
│ │ ├── starts/
│ │ │ └── new.html.erb
│ │ └── transfers/
│ │ └── show.html.erb
│ ├── signups/
│ │ ├── completions/
│ │ │ └── new.html.erb
│ │ └── new.html.erb
│ ├── tags/
│ │ ├── _tag.json.jbuilder
│ │ ├── index.html.erb
│ │ └── index.json.jbuilder
│ ├── user_mailer/
│ │ └── email_change_confirmation.html.erb
│ ├── users/
│ │ ├── _access_tokens.html.erb
│ │ ├── _activity_timeline.html.erb
│ │ ├── _attachable.html.erb
│ │ ├── _data_export.html.erb
│ │ ├── _theme.html.erb
│ │ ├── _transfer.html.erb
│ │ ├── _user.json.jbuilder
│ │ ├── avatars/
│ │ │ └── show.svg.erb
│ │ ├── data_exports/
│ │ │ └── show.html.erb
│ │ ├── edit.html.erb
│ │ ├── email_addresses/
│ │ │ ├── confirmations/
│ │ │ │ ├── invalid_token.html.erb
│ │ │ │ └── show.html.erb
│ │ │ ├── create.html.erb
│ │ │ └── new.html.erb
│ │ ├── events/
│ │ │ └── show.html.erb
│ │ ├── index.json.jbuilder
│ │ ├── joins/
│ │ │ └── new.html.erb
│ │ ├── show.html.erb
│ │ ├── show.json.jbuilder
│ │ └── verifications/
│ │ └── new.html.erb
│ └── webhooks/
│ ├── _delivery.html.erb
│ ├── _webhook.html.erb
│ ├── _webhook.json.jbuilder
│ ├── edit.html.erb
│ ├── event.html.erb
│ ├── event.json.jbuilder
│ ├── form/
│ │ └── _actions.html.erb
│ ├── index.html.erb
│ ├── index.json.jbuilder
│ ├── new.html.erb
│ ├── show.html.erb
│ └── show.json.jbuilder
├── bin/
│ ├── brakeman
│ ├── bundle-both
│ ├── bundle-drift
│ ├── bundler-audit
│ ├── ci
│ ├── dev
│ ├── docker-entrypoint
│ ├── gitleaks-audit
│ ├── importmap
│ ├── jobs
│ ├── kamal
│ ├── minio-setup
│ ├── notify_dash_of_deployment
│ ├── rails
│ ├── rake
│ ├── rubocop
│ ├── setup
│ └── thrust
├── config/
│ ├── application.rb
│ ├── boot.rb
│ ├── brakeman.ignore
│ ├── cable.yml
│ ├── cache.yml
│ ├── ci.rb
│ ├── database.mysql.yml
│ ├── database.sqlite.yml
│ ├── database.yml
│ ├── deploy.yml
│ ├── environment.rb
│ ├── environments/
│ │ ├── beta.rb
│ │ ├── development.rb
│ │ ├── production.rb
│ │ ├── staging.rb
│ │ └── test.rb
│ ├── importmap.rb
│ ├── initializers/
│ │ ├── action_text.rb
│ │ ├── active_job.rb
│ │ ├── active_storage.rb
│ │ ├── active_storage_no_reuse.rb
│ │ ├── active_storage_purge_on_last_attachment.rb
│ │ ├── assets.rb
│ │ ├── autotuner.rb
│ │ ├── content_security_policy.rb
│ │ ├── database_role_logging.rb
│ │ ├── error_context.rb
│ │ ├── extensions.rb
│ │ ├── filter_parameter_logging.rb
│ │ ├── inflections.rb
│ │ ├── mission_control.rb
│ │ ├── multi_db.rb
│ │ ├── multi_tenant.rb
│ │ ├── passkeys.rb
│ │ ├── permissions_policy.rb
│ │ ├── push_notifications.rb
│ │ ├── rack_mini_profiler.rb
│ │ ├── sanitization.rb
│ │ ├── sqlite_schema_dumper.rb
│ │ ├── table_definition_column_limits.rb
│ │ ├── tenanting/
│ │ │ ├── account_slug.rb
│ │ │ └── turbo.rb
│ │ ├── uuid_framework_models.rb
│ │ ├── uuid_primary_keys.rb
│ │ ├── vapid.rb
│ │ ├── vips.rb
│ │ └── web_push.rb
│ ├── locales/
│ │ └── en.yml
│ ├── passkey_aaguids.yml
│ ├── puma.rb
│ ├── queue.yml
│ ├── recurring.yml
│ ├── routes.rb
│ ├── storage.oss.yml
│ └── storage.yml
├── config.ru
├── db/
│ ├── cable_schema.rb
│ ├── cache_schema.rb
│ ├── migrate/
│ │ ├── 20251111122540_initial_schema.rb
│ │ ├── 20251111153019_add_number_to_cards.rb
│ │ ├── 20251112093037_create_search_indices.rb
│ │ ├── 20251112184932_remove_join_code_from_memberships.rb
│ │ ├── 20251113111501_drop_memberships.rb
│ │ ├── 20251113160907_add_missing_account_id_columns.rb
│ │ ├── 20251113163145_ensure_account_id_index.rb
│ │ ├── 20251113190256_create_search_record_shards.rb
│ │ ├── 20251114084325_drop_search_results.rb
│ │ ├── 20251114183203_ensure_an_identit_can_only_have_one_user_in_an_account.rb
│ │ ├── 20251117190817_change_endpoint_to_text_in_push_subscriptions.rb
│ │ ├── 20251117192434_change_external_account_id_to_bigint_in_accounts.rb
│ │ ├── 20251117202517_change_usage_limit_to_bigint_in_account_join_codes.rb
│ │ ├── 20251120110206_add_search_records.rb
│ │ ├── 20251120194700_remove_all_foreign_key_constraints.rb
│ │ ├── 20251120203100_add_unique_index_to_card_activity_spikes_on_card_id.rb
│ │ ├── 20251121092508_add_account_key_to_search_records.rb
│ │ ├── 20251121112416_remove_old_fulltext_indexes_from_search_records.rb
│ │ ├── 20251125110629_increase_user_agent_length.rb
│ │ ├── 20251125130010_add_a_staff_flag_to_identities.rb
│ │ ├── 20251127000001_create_account_external_id_sequences.rb
│ │ ├── 20251129110120_add_purpose_to_magic_links.rb
│ │ ├── 20251129175717_promote_first_admin_to_owner.rb
│ │ ├── 20251201100607_create_account_exports.rb
│ │ ├── 20251201132341_create_identity_access_tokens.rb
│ │ ├── 20251205010536_add_verified_at_to_users.rb
│ │ ├── 20251205205826_create_storage_tables.rb
│ │ ├── 20251210054934_add_blob_id_and_audit_context_to_storage_entries.rb
│ │ ├── 20251219120755_drop_card_engagements.rb
│ │ ├── 20251223000001_rename_account_exports_to_exports.rb
│ │ ├── 20251223000002_create_account_imports.rb
│ │ ├── 20251224092315_create_account_cancellations.rb
│ │ ├── 20260121155752_make_reactions_polymorphic.rb
│ │ ├── 20260206104338_add_card_id_to_notifications.rb
│ │ ├── 20260209165805_notifications_data_migration.rb
│ │ ├── 20260211122517_add_failure_reason_to_account_imports.rb
│ │ ├── 20260212102026_fix_notifications_ordered_index.rb
│ │ ├── 20260213154740_create_action_pack_passkeys.rb
│ │ ├── 20260213170100_add_created_at_index_to_webhook_deliveries.rb
│ │ └── 20260218120000_restore_unique_index_on_board_publication_key.rb
│ ├── queue_schema.rb
│ ├── schema.rb
│ ├── schema_sqlite.rb
│ ├── seeds/
│ │ ├── 37signals.rb
│ │ ├── cleanslate.rb
│ │ └── honcho.rb
│ └── seeds.rb
├── docs/
│ ├── API.md
│ ├── development.md
│ ├── docker-deployment.md
│ └── kamal-deployment.md
├── lib/
│ ├── action_pack/
│ │ ├── passkey/
│ │ │ ├── challenges_controller.rb
│ │ │ ├── form_helper.rb
│ │ │ ├── holder.rb
│ │ │ └── request.rb
│ │ ├── passkey.rb
│ │ ├── railtie.rb
│ │ ├── web_authn/
│ │ │ ├── authenticator/
│ │ │ │ ├── assertion_response.rb
│ │ │ │ ├── attestation.rb
│ │ │ │ ├── attestation_response.rb
│ │ │ │ ├── attestation_verifiers/
│ │ │ │ │ └── none.rb
│ │ │ │ ├── data.rb
│ │ │ │ └── response.rb
│ │ │ ├── cbor_decoder.rb
│ │ │ ├── cose_key.rb
│ │ │ ├── current.rb
│ │ │ ├── public_key_credential/
│ │ │ │ ├── creation_options.rb
│ │ │ │ ├── options.rb
│ │ │ │ └── request_options.rb
│ │ │ ├── public_key_credential.rb
│ │ │ └── relying_party.rb
│ │ └── web_authn.rb
│ ├── assets/
│ │ └── .keep
│ ├── auto_link_scrubber.rb
│ ├── deployment/
│ │ └── database_resolver.rb
│ ├── deployment.rb
│ ├── fizzy.rb
│ ├── rails_ext/
│ │ ├── action_mailer_mail_delivery_job.rb
│ │ ├── action_pack_passkey_infer_name_from_aaguid.rb
│ │ ├── active_record_date_arithmetic.rb
│ │ ├── active_record_replica_support.rb
│ │ ├── active_record_uuid_type.rb
│ │ ├── active_storage_analyze_job_skip_detached.rb
│ │ ├── active_storage_analyze_job_suppress_broadcasts.rb
│ │ ├── active_storage_authorization.rb
│ │ ├── active_storage_blob_service_url_for_direct_upload_expiry.rb
│ │ ├── active_support_array_conversions.rb
│ │ ├── prepend_order.rb
│ │ └── string.rb
│ ├── tasks/
│ │ ├── dev.rake
│ │ ├── saas.rake
│ │ └── search.rake
│ └── web_push/
│ ├── notification.rb
│ └── pool.rb
├── log/
│ └── .keep
├── public/
│ ├── 400.html
│ ├── 404.html
│ ├── 406-unsupported-browser.html
│ ├── 422.html
│ ├── 500.html
│ ├── error.css
│ └── robots.txt
├── saas/
│ ├── .kamal/
│ │ ├── hooks/
│ │ │ ├── post-deploy
│ │ │ └── pre-connect
│ │ ├── secrets.beta
│ │ ├── secrets.production
│ │ └── secrets.staging
│ ├── Dockerfile
│ ├── LICENSE.md
│ ├── README.md
│ ├── Rakefile
│ ├── app/
│ │ ├── assets/
│ │ │ └── images/
│ │ │ └── fizzy/
│ │ │ └── saas/
│ │ │ └── .keep
│ │ ├── controllers/
│ │ │ ├── admin/
│ │ │ │ ├── audits_controller.rb
│ │ │ │ └── stats_controller.rb
│ │ │ ├── concerns/
│ │ │ │ └── card/
│ │ │ │ ├── storage_limited/
│ │ │ │ │ ├── commenting.rb
│ │ │ │ │ ├── creation.rb
│ │ │ │ │ └── publishing.rb
│ │ │ │ └── storage_limited.rb
│ │ │ └── my/
│ │ │ └── devices_controller.rb
│ │ ├── jobs/
│ │ │ └── application_push_notification_job.rb
│ │ ├── models/
│ │ │ ├── account/
│ │ │ │ ├── storage_exception.rb
│ │ │ │ └── storage_limited.rb
│ │ │ ├── application_push_device.rb
│ │ │ ├── application_push_notification.rb
│ │ │ ├── identity/
│ │ │ │ └── devices.rb
│ │ │ ├── notification/
│ │ │ │ └── push_target/
│ │ │ │ └── native.rb
│ │ │ ├── saas_record.rb
│ │ │ ├── session/
│ │ │ │ └── devices.rb
│ │ │ └── subscription.rb
│ │ └── views/
│ │ ├── admin/
│ │ │ └── stats/
│ │ │ └── show.html.erb
│ │ ├── cards/
│ │ │ ├── comments/
│ │ │ │ └── saas/
│ │ │ │ ├── _new.html.erb
│ │ │ │ └── _storage_limit_exceeded.html.erb
│ │ │ └── container/
│ │ │ └── footer/
│ │ │ └── saas/
│ │ │ ├── _create.html.erb
│ │ │ ├── _storage_limit_exceeded.html.erb
│ │ │ └── _storage_limit_notice.html.erb
│ │ ├── layouts/
│ │ │ └── fizzy/
│ │ │ └── saas/
│ │ │ └── application.html.erb
│ │ ├── my/
│ │ │ └── devices/
│ │ │ └── index.html.erb
│ │ ├── notifications/
│ │ │ └── settings/
│ │ │ └── _native_devices.html.erb
│ │ └── signup/
│ │ ├── completions/
│ │ │ └── new.html.erb
│ │ └── new.html.erb
│ ├── bin/
│ │ ├── broadcast_to_bc
│ │ └── setup
│ ├── config/
│ │ ├── database.yml
│ │ ├── deploy.beta.yml
│ │ ├── deploy.beta1.yml
│ │ ├── deploy.beta2.yml
│ │ ├── deploy.beta3.yml
│ │ ├── deploy.beta4.yml
│ │ ├── deploy.production.yml
│ │ ├── deploy.staging.yml
│ │ ├── deploy.yml
│ │ ├── environments/
│ │ │ ├── beta.rb
│ │ │ ├── development.rb
│ │ │ ├── production.rb
│ │ │ └── staging.rb
│ │ ├── push.yml
│ │ ├── routes.rb
│ │ └── storage.yml
│ ├── db/
│ │ ├── migrate/
│ │ │ ├── 20251202200249_create_console1984_tables.console1984.rb
│ │ │ ├── 20251202205753_create_auditing_tables.audits1984.rb
│ │ │ ├── 20251203144630_create_account_subscriptions.rb
│ │ │ ├── 20251215140000_create_account_overridden_limits.rb
│ │ │ ├── 20251215160000_create_account_billing_waivers.rb
│ │ │ ├── 20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb
│ │ │ ├── 20251216000000_add_bytes_used_to_account_overridden_limits.rb
│ │ │ ├── 20260114203313_create_action_push_native_devices.rb
│ │ │ ├── 20260126230838_create_auditor_tokens.audits1984.rb
│ │ │ ├── 20260317000000_drop_billing_tables.rb
│ │ │ └── 20260319142914_create_account_storage_exceptions.rb
│ │ └── saas_schema.rb
│ ├── exe/
│ │ └── push-dev
│ ├── fizzy-saas.gemspec
│ ├── lib/
│ │ ├── fizzy/
│ │ │ ├── saas/
│ │ │ │ ├── authorization.rb
│ │ │ │ ├── engine.rb
│ │ │ │ ├── gvl_instrumentation.rb
│ │ │ │ ├── metrics.rb
│ │ │ │ ├── signup.rb
│ │ │ │ ├── testing.rb
│ │ │ │ ├── transaction_pinning.rb
│ │ │ │ ├── true_client_ip.rb
│ │ │ │ └── version.rb
│ │ │ └── saas.rb
│ │ ├── rails_ext/
│ │ │ └── active_record_tasks_database_tasks.rb
│ │ ├── tasks/
│ │ │ └── fizzy/
│ │ │ └── saas_tasks.rake
│ │ └── yabeda/
│ │ ├── gvl.rb
│ │ └── solid_queue.rb
│ ├── public/
│ │ └── .well-known/
│ │ ├── apple-app-site-association
│ │ └── assetlinks.json
│ ├── script/
│ │ ├── configure-lb-beta.sh
│ │ ├── configure-lb-production.sh
│ │ └── configure-lb-staging.sh
│ └── test/
│ ├── controllers/
│ │ ├── .keep
│ │ ├── admin/
│ │ │ ├── audits_controller_test.rb
│ │ │ └── stats_controller_test.rb
│ │ ├── card/
│ │ │ ├── storage_limited/
│ │ │ │ ├── commenting_test.rb
│ │ │ │ ├── creation_test.rb
│ │ │ │ └── publishing_test.rb
│ │ │ └── storage_limited_test.rb
│ │ ├── comment/
│ │ │ └── storage_limited_test.rb
│ │ ├── my/
│ │ │ └── devices_controller_test.rb
│ │ └── non_production_remote_access_test.rb
│ ├── fixtures/
│ │ ├── application_push_devices.yml
│ │ └── files/
│ │ └── .keep
│ ├── helpers/
│ │ └── .keep
│ ├── integration/
│ │ └── .keep
│ ├── lib/
│ │ └── true_client_ip_test.rb
│ ├── mailers/
│ │ └── .keep
│ └── models/
│ ├── account/
│ │ ├── storage_exception_test.rb
│ │ └── storage_limited_test.rb
│ ├── identity_test.rb
│ ├── notification/
│ │ └── push_target/
│ │ └── native_test.rb
│ ├── session/
│ │ └── devices_test.rb
│ └── signup_test.rb
├── script/
│ ├── create-identities.rb
│ ├── fetch-prod-db.rb
│ ├── fix-active-storage-links.rb
│ ├── import-sqlite-database.rb
│ ├── load-prod-db-in-dev.rb
│ ├── maintenance/
│ │ ├── fix_cross_account_taggings.rb
│ │ ├── remove_duplicated_search_queries.rb
│ │ └── remove_duplicated_tags.rb
│ ├── migrations/
│ │ ├── 20250924-populate-identities.rb
│ │ ├── 20251028-populate_membership_id_on_users.rb
│ │ ├── 20251029-populate-column-positions.rb
│ │ ├── 20251205-backfill-verified-at.rb
│ │ ├── 20260123-remove-draft-cards-from-search-index.rb
│ │ ├── 20260204-fix-misplaced-comment-events.rb
│ │ ├── backfill-storage-ledger.rb
│ │ ├── convert-absolute-attachment-urls-to-relative.rb
│ │ ├── convert-relative-attachment-urls-to-absolute.rb
│ │ ├── copy-blobs-to-pure.rb
│ │ ├── fill_account_closure_reasons.rb
│ │ ├── generate_comments_from_events.rb
│ │ ├── migrate-content-to-slugged-urls.rb
│ │ ├── migrate-disk-service-blobs.rb
│ │ ├── migrate_to_flat_card_urls.rb
│ │ ├── migrate_to_new_cards_url_scheme.rb
│ │ ├── populate_columns_from_workflow_stages.rb
│ │ ├── renaming/
│ │ │ ├── content.rb
│ │ │ └── files.rb
│ │ ├── reset_boards_ids.rb
│ │ ├── reset_cards_ids.rb
│ │ └── split-sibling-paragraphs-with-p-br.rb
│ ├── populate.rb
│ └── remove-lb-admin-production.sh
├── storage/
│ └── .keep
├── test/
│ ├── application_system_test_case.rb
│ ├── channels/
│ │ └── application_cable/
│ │ └── connection_test.rb
│ ├── controllers/
│ │ ├── account/
│ │ │ └── cancellations_controller_test.rb
│ │ ├── accounts/
│ │ │ ├── entropies_controller_test.rb
│ │ │ ├── exports_controller_test.rb
│ │ │ ├── join_codes_controller_test.rb
│ │ │ └── settings_controller_test.rb
│ │ ├── active_storage/
│ │ │ └── direct_uploads_controller_test.rb
│ │ ├── admin/
│ │ │ └── mission_control_test.rb
│ │ ├── allow_browser_test.rb
│ │ ├── api/
│ │ │ └── flat_json_params_test.rb
│ │ ├── api_test.rb
│ │ ├── boards/
│ │ │ ├── columns/
│ │ │ │ ├── closeds_controller_test.rb
│ │ │ │ ├── not_nows_controller_test.rb
│ │ │ │ └── streams_controller_test.rb
│ │ │ ├── columns_controller_test.rb
│ │ │ ├── entropies_controller_test.rb
│ │ │ ├── involvements_controller_test.rb
│ │ │ └── publications_controller_test.rb
│ │ ├── boards_controller_test.rb
│ │ ├── cards/
│ │ │ ├── assignments_controller_test.rb
│ │ │ ├── boards_controller_test.rb
│ │ │ ├── closures_controller_test.rb
│ │ │ ├── comments/
│ │ │ │ └── reactions_controller_test.rb
│ │ │ ├── comments_controller_test.rb
│ │ │ ├── drafts_controller_test.rb
│ │ │ ├── goldnesses_controller_test.rb
│ │ │ ├── images_controller_test.rb
│ │ │ ├── not_nows_controller_test.rb
│ │ │ ├── pins_controller_test.rb
│ │ │ ├── previews_controller_test.rb
│ │ │ ├── publishes_controller_test.rb
│ │ │ ├── reactions_controller_test.rb
│ │ │ ├── readings_controller_test.rb
│ │ │ ├── self_assignments_controller_test.rb
│ │ │ ├── steps_controller_test.rb
│ │ │ ├── taggings_controller_test.rb
│ │ │ ├── triages_controller_test.rb
│ │ │ └── watches_controller_test.rb
│ │ ├── cards_controller_test.rb
│ │ ├── client_configurations_controller_test.rb
│ │ ├── columns/
│ │ │ ├── cards/
│ │ │ │ └── drops/
│ │ │ │ ├── closures_controller_test.rb
│ │ │ │ ├── columns_controller_test.rb
│ │ │ │ ├── not_nows_controller_test.rb
│ │ │ │ └── streams_controller_test.rb
│ │ │ ├── left_positions_controller_test.rb
│ │ │ └── right_positions_controller_test.rb
│ │ ├── concerns/
│ │ │ ├── block_search_engine_indexing_test.rb
│ │ │ ├── current_timezone_test.rb
│ │ │ ├── request_forgery_protection_test.rb
│ │ │ └── set_platform_test.rb
│ │ ├── controller_authentication_test.rb
│ │ ├── events/
│ │ │ └── day_timeline/
│ │ │ └── columns_controller_test.rb
│ │ ├── events_controller_test.rb
│ │ ├── filters_controller_test.rb
│ │ ├── join_codes_controller_test.rb
│ │ ├── landings_controller_test.rb
│ │ ├── my/
│ │ │ ├── access_tokens_controller_test.rb
│ │ │ ├── identities_controller_test.rb
│ │ │ ├── menus_controller_test.rb
│ │ │ ├── passkey_challenges_controller_test.rb
│ │ │ ├── passkeys_controller_test.rb
│ │ │ ├── pins_controller_test.rb
│ │ │ └── timezones_controller_test.rb
│ │ ├── notifications/
│ │ │ ├── bulk_readings_controller_test.rb
│ │ │ ├── readings_controller_test.rb
│ │ │ ├── settings_controller_test.rb
│ │ │ ├── trays_controller_test.rb
│ │ │ └── unsubscribes_controller_test.rb
│ │ ├── notifications_controller_test.rb
│ │ ├── prompts/
│ │ │ ├── boards/
│ │ │ │ └── users_controller_test.rb
│ │ │ ├── cards_controller_test.rb
│ │ │ ├── tags_controller_test.rb
│ │ │ └── users_controller_test.rb
│ │ ├── public/
│ │ │ ├── boards/
│ │ │ │ ├── columns/
│ │ │ │ │ ├── closeds_controller_test.rb
│ │ │ │ │ ├── not_nows_controller_test.rb
│ │ │ │ │ └── streams_controller_test.rb
│ │ │ │ └── columns_controller_test.rb
│ │ │ ├── boards_controller_test.rb
│ │ │ └── cards_controller_test.rb
│ │ ├── qr_codes_controller_test.rb
│ │ ├── searches/
│ │ │ └── queries_controller_test.rb
│ │ ├── searches_controller_test.rb
│ │ ├── sessions/
│ │ │ ├── magic_links_controller_test.rb
│ │ │ ├── menus_controller_test.rb
│ │ │ ├── passkeys_controller_test.rb
│ │ │ └── transfers_controller_test.rb
│ │ ├── sessions_controller_test.rb
│ │ ├── signup/
│ │ │ └── completions_controller_test.rb
│ │ ├── signups_controller_test.rb
│ │ ├── tags_controller_test.rb
│ │ ├── users/
│ │ │ ├── avatars_controller_test.rb
│ │ │ ├── data_exports_controller_test.rb
│ │ │ ├── email_addresses/
│ │ │ │ └── confirmations_controller_test.rb
│ │ │ ├── email_addresses_controller_test.rb
│ │ │ ├── events_controller_test.rb
│ │ │ ├── joins_controller_test.rb
│ │ │ ├── push_subscriptions_controller_test.rb
│ │ │ ├── roles_controller_test.rb
│ │ │ └── verifications_controller_test.rb
│ │ ├── users_controller_test.rb
│ │ ├── webhooks/
│ │ │ └── activations_controller_test.rb
│ │ └── webhooks_controller_test.rb
│ ├── fixtures/
│ │ ├── accesses.yml
│ │ ├── account/
│ │ │ └── join_codes.yml
│ │ ├── accounts.yml
│ │ ├── action_text/
│ │ │ └── rich_texts.yml
│ │ ├── assignees_filters.yml
│ │ ├── assignments.yml
│ │ ├── boards.yml
│ │ ├── card/
│ │ │ └── goldnesses.yml
│ │ ├── cards.yml
│ │ ├── closures.yml
│ │ ├── columns.yml
│ │ ├── comments.yml
│ │ ├── entropies.yml
│ │ ├── events.yml
│ │ ├── exports.yml
│ │ ├── filters.yml
│ │ ├── filters_tags.yml
│ │ ├── identities.yml
│ │ ├── identity/
│ │ │ └── access_tokens.yml
│ │ ├── mentions.yml
│ │ ├── notifications.yml
│ │ ├── pins.yml
│ │ ├── reactions.yml
│ │ ├── sessions.yml
│ │ ├── taggings.yml
│ │ ├── tags.yml
│ │ ├── user/
│ │ │ └── settings.yml
│ │ ├── users.yml
│ │ ├── watches.yml
│ │ ├── webhook/
│ │ │ ├── delinquency_trackers.yml
│ │ │ └── deliveries.yml
│ │ └── webhooks.yml
│ ├── helpers/
│ │ ├── .keep
│ │ ├── action_text_rendering_test.rb
│ │ ├── application_helper_test.rb
│ │ ├── entropy_helper_test.rb
│ │ ├── excerpt_helper_test.rb
│ │ ├── hotkeys_helper_test.rb
│ │ └── html_helper_test.rb
│ ├── integration/
│ │ ├── active_storage_authorization_test.rb
│ │ ├── blob_key_traversal_test.rb
│ │ ├── card_preview_boost_count_test.rb
│ │ └── notifications_test.rb
│ ├── jobs/
│ │ ├── account/
│ │ │ ├── data_import_job_test.rb
│ │ │ └── incinerate_due_job_test.rb
│ │ ├── delete_unused_tags_job_test.rb
│ │ └── storage/
│ │ ├── materialize_job_test.rb
│ │ └── reconcile_job_test.rb
│ ├── lib/
│ │ ├── action_pack/
│ │ │ ├── passkey_test.rb
│ │ │ └── web_authn/
│ │ │ ├── authenticator/
│ │ │ │ ├── assertion_response_test.rb
│ │ │ │ ├── attestation_response_test.rb
│ │ │ │ ├── attestation_test.rb
│ │ │ │ ├── attestation_verifiers/
│ │ │ │ │ └── none_test.rb
│ │ │ │ ├── data_test.rb
│ │ │ │ └── response_test.rb
│ │ │ ├── cbor_decoder_test.rb
│ │ │ ├── cose_key_test.rb
│ │ │ ├── public_key_credential/
│ │ │ │ ├── creation_options_test.rb
│ │ │ │ └── request_options_test.rb
│ │ │ └── relying_party_test.rb
│ │ ├── rails_ext/
│ │ │ ├── action_pack_passkey_infer_name_from_aaguid_test.rb
│ │ │ ├── active_record_uuid_type_test.rb
│ │ │ ├── active_storage_analyze_job_skip_detached_test.rb
│ │ │ ├── active_storage_blob_service_url_for_direct_upload_expiry_test.rb
│ │ │ └── string_test.rb
│ │ └── web_push/
│ │ └── persistent_request_test.rb
│ ├── mailers/
│ │ ├── .keep
│ │ ├── account_mailer_test.rb
│ │ ├── export_mailer_test.rb
│ │ ├── import_mailer_test.rb
│ │ ├── magic_link_mailer_test.rb
│ │ ├── notification/
│ │ │ └── bundle_mailer_test.rb
│ │ ├── previews/
│ │ │ ├── export_mailer_preview.rb
│ │ │ ├── magic_link_mailer_preview.rb
│ │ │ ├── notification/
│ │ │ │ └── bundle_mailer_preview.rb
│ │ │ └── user_mailer_preview.rb
│ │ └── smtp_delivery_error_test.rb
│ ├── middleware/
│ │ └── account_slug_extractor_test.rb
│ ├── models/
│ │ ├── access_test.rb
│ │ ├── account/
│ │ │ ├── cancellable_test.rb
│ │ │ ├── cancellation_test.rb
│ │ │ ├── data_transfer/
│ │ │ │ ├── action_text/
│ │ │ │ │ └── rich_text_record_set_test.rb
│ │ │ │ ├── active_storage/
│ │ │ │ │ ├── blob_record_set_test.rb
│ │ │ │ │ └── file_record_set_test.rb
│ │ │ │ └── record_set_test.rb
│ │ │ ├── export_test.rb
│ │ │ ├── external_id_sequence_test.rb
│ │ │ ├── import_test.rb
│ │ │ ├── incineratable_test.rb
│ │ │ ├── join_code_test.rb
│ │ │ ├── multi_tenantable_test.rb
│ │ │ └── seedeable_test.rb
│ │ ├── account_test.rb
│ │ ├── application_platform_test.rb
│ │ ├── assignment_test.rb
│ │ ├── board/
│ │ │ ├── accessible_test.rb
│ │ │ ├── cards_test.rb
│ │ │ └── publishable_test.rb
│ │ ├── card/
│ │ │ ├── activity_spike/
│ │ │ │ └── detector_test.rb
│ │ │ ├── assignable_test.rb
│ │ │ ├── closeable_test.rb
│ │ │ ├── colored_test.rb
│ │ │ ├── commentable_test.rb
│ │ │ ├── entropic_test.rb
│ │ │ ├── eventable/
│ │ │ │ └── system_commenter_test.rb
│ │ │ ├── eventable_test.rb
│ │ │ ├── exportable_test.rb
│ │ │ ├── golden_test.rb
│ │ │ ├── messages_test.rb
│ │ │ ├── pinnable_test.rb
│ │ │ ├── postponable_test.rb
│ │ │ ├── readable_test.rb
│ │ │ ├── searchable_test.rb
│ │ │ ├── stallable_test.rb
│ │ │ ├── statuses_test.rb
│ │ │ ├── taggable_test.rb
│ │ │ ├── triageable_test.rb
│ │ │ └── watchable_test.rb
│ │ ├── card_test.rb
│ │ ├── column/
│ │ │ ├── colored_test.rb
│ │ │ └── positioned_test.rb
│ │ ├── column_limits_test.rb
│ │ ├── column_test.rb
│ │ ├── comment/
│ │ │ └── searchable_test.rb
│ │ ├── comment_test.rb
│ │ ├── concerns/
│ │ │ └── mentions_test.rb
│ │ ├── entropy_test.rb
│ │ ├── event/
│ │ │ └── description_test.rb
│ │ ├── filter/
│ │ │ └── search_test.rb
│ │ ├── filter_test.rb
│ │ ├── identity/
│ │ │ ├── access_token_test.rb
│ │ │ ├── joinable_test.rb
│ │ │ └── transferable_test.rb
│ │ ├── identity_test.rb
│ │ ├── magic_link/
│ │ │ └── code_test.rb
│ │ ├── magic_link_test.rb
│ │ ├── notification/
│ │ │ ├── bundle_test.rb
│ │ │ ├── push_target/
│ │ │ │ └── web_test.rb
│ │ │ └── pushable_test.rb
│ │ ├── notification_test.rb
│ │ ├── notifier/
│ │ │ ├── event_notifier_test.rb
│ │ │ └── mention_notifier_test.rb
│ │ ├── push/
│ │ │ └── subscription_test.rb
│ │ ├── qr_code_link_test.rb
│ │ ├── reaction_test.rb
│ │ ├── search/
│ │ │ ├── highlighter_test.rb
│ │ │ └── stemmer_test.rb
│ │ ├── search_test.rb
│ │ ├── signup/
│ │ │ └── account_name_generator_test.rb
│ │ ├── signup_test.rb
│ │ ├── ssrf_protection_test.rb
│ │ ├── storage/
│ │ │ ├── attachment_tracking_test.rb
│ │ │ ├── entry_test.rb
│ │ │ ├── no_reuse_test.rb
│ │ │ ├── total_test.rb
│ │ │ ├── totaled_test.rb
│ │ │ └── tracked_test.rb
│ │ ├── tag_test.rb
│ │ ├── time_window_parser_test.rb
│ │ ├── user/
│ │ │ ├── accessor_test.rb
│ │ │ ├── avatar_test.rb
│ │ │ ├── configurable_test.rb
│ │ │ ├── data_export_test.rb
│ │ │ ├── email_address_changeable_test.rb
│ │ │ ├── mentionable_test.rb
│ │ │ ├── named_test.rb
│ │ │ ├── notifiable_test.rb
│ │ │ ├── role_test.rb
│ │ │ ├── searcher_test.rb
│ │ │ └── settings_test.rb
│ │ ├── user_test.rb
│ │ ├── webhook/
│ │ │ ├── delinquency_tracker_test.rb
│ │ │ ├── delivery_test.rb
│ │ │ └── triggerable_test.rb
│ │ ├── webhook_test.rb
│ │ └── zip_file_test.rb
│ ├── routes_test.rb
│ ├── system/
│ │ ├── .keep
│ │ ├── back_link_navigation_test.rb
│ │ ├── markdown_paste_test.rb
│ │ └── smoke_test.rb
│ ├── test_helper.rb
│ ├── test_helpers/
│ │ ├── action_text_test_helper.rb
│ │ ├── caching_test_helper.rb
│ │ ├── card_activity_test_helper.rb
│ │ ├── card_test_helper.rb
│ │ ├── change_test_helper.rb
│ │ ├── command_test_helper.rb
│ │ ├── search_test_helper.rb
│ │ ├── session_test_helper.rb
│ │ ├── vcr_test_helper.rb
│ │ └── webauthn_test_helper.rb
│ └── webmock_ipaddr_extension.rb
├── tmp/
│ └── .keep
└── vendor/
└── javascript/
├── @hotwired--hotwire-native-bridge.js
└── @rails--request.js
Showing preview only (268K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (3236 symbols across 850 files)
FILE: app/channels/application_cable/connection.rb
type ApplicationCable (line 1) | module ApplicationCable
class Connection (line 2) | class Connection < ActionCable::Connection::Base
method connect (line 5) | def connect
method set_current_user (line 10) | def set_current_user
method find_session_by_cookie (line 18) | def find_session_by_cookie
FILE: app/controllers/account/cancellations_controller.rb
class Account::CancellationsController (line 1) | class Account::CancellationsController < ApplicationController
method create (line 4) | def create
method ensure_owner (line 10) | def ensure_owner
FILE: app/controllers/account/entropies_controller.rb
class Account::EntropiesController (line 1) | class Account::EntropiesController < ApplicationController
method update (line 6) | def update
method entropy_params (line 19) | def entropy_params
FILE: app/controllers/account/exports_controller.rb
class Account::ExportsController (line 1) | class Account::ExportsController < ApplicationController
method show (line 8) | def show
method create (line 15) | def create
method ensure_admin_or_owner (line 26) | def ensure_admin_or_owner
method ensure_export_limit_not_exceeded (line 30) | def ensure_export_limit_not_exceeded
method set_export (line 34) | def set_export
FILE: app/controllers/account/imports_controller.rb
class Account::ImportsController (line 1) | class Account::ImportsController < ApplicationController
method new (line 9) | def new
method create (line 12) | def create
method show (line 22) | def show
method set_import (line 26) | def set_import
method ensure_accessed_by_owner (line 30) | def ensure_accessed_by_owner
method start_import (line 34) | def start_import(account)
FILE: app/controllers/account/join_codes_controller.rb
class Account::JoinCodesController (line 1) | class Account::JoinCodesController < ApplicationController
method show (line 7) | def show
method edit (line 10) | def edit
method update (line 13) | def update
method destroy (line 27) | def destroy
method set_join_code (line 37) | def set_join_code
method join_code_params (line 41) | def join_code_params
FILE: app/controllers/account/settings_controller.rb
class Account::SettingsController (line 1) | class Account::SettingsController < ApplicationController
method show (line 7) | def show
method update (line 14) | def update
method set_account (line 24) | def set_account
method account_params (line 28) | def account_params
FILE: app/controllers/admin_controller.rb
class AdminController (line 1) | class AdminController < ApplicationController
FILE: app/controllers/application_controller.rb
class ApplicationController (line 1) | class ApplicationController < ActionController::Base
FILE: app/controllers/boards/columns/closeds_controller.rb
class Boards::Columns::ClosedsController (line 1) | class Boards::Columns::ClosedsController < ApplicationController
method show (line 4) | def show
FILE: app/controllers/boards/columns/not_nows_controller.rb
class Boards::Columns::NotNowsController (line 1) | class Boards::Columns::NotNowsController < ApplicationController
method show (line 4) | def show
FILE: app/controllers/boards/columns/streams_controller.rb
class Boards::Columns::StreamsController (line 1) | class Boards::Columns::StreamsController < ApplicationController
method show (line 4) | def show
FILE: app/controllers/boards/columns_controller.rb
class Boards::ColumnsController (line 1) | class Boards::ColumnsController < ApplicationController
method index (line 8) | def index
method show (line 13) | def show
method create (line 18) | def create
method update (line 27) | def update
method destroy (line 36) | def destroy
method set_column (line 46) | def set_column
method column_params (line 50) | def column_params
FILE: app/controllers/boards/entropies_controller.rb
class Boards::EntropiesController (line 1) | class Boards::EntropiesController < ApplicationController
method update (line 8) | def update
method entropy_params (line 20) | def entropy_params
FILE: app/controllers/boards/involvements_controller.rb
class Boards::InvolvementsController (line 1) | class Boards::InvolvementsController < ApplicationController
method update (line 4) | def update
FILE: app/controllers/boards/publications_controller.rb
class Boards::PublicationsController (line 1) | class Boards::PublicationsController < ApplicationController
method create (line 6) | def create
method destroy (line 15) | def destroy
FILE: app/controllers/boards_controller.rb
class BoardsController (line 1) | class BoardsController < ApplicationController
method index (line 9) | def index
method show (line 14) | def show
method new (line 22) | def new
method create (line 26) | def create
method edit (line 35) | def edit
method update (line 41) | def update
method destroy (line 57) | def destroy
method set_board (line 67) | def set_board
method ensure_permission_to_admin_board (line 71) | def ensure_permission_to_admin_board
method grantees_changed? (line 77) | def grantees_changed?
method show_filtered_cards (line 81) | def show_filtered_cards
method show_columns (line 86) | def show_columns
method board_params (line 92) | def board_params
method grantees (line 96) | def grantees
method revokees (line 100) | def revokees
method grantee_ids (line 104) | def grantee_ids
FILE: app/controllers/cards/assignments_controller.rb
class Cards::AssignmentsController (line 1) | class Cards::AssignmentsController < ApplicationController
method new (line 4) | def new
method create (line 10) | def create
FILE: app/controllers/cards/boards_controller.rb
class Cards::BoardsController (line 1) | class Cards::BoardsController < ApplicationController
method edit (line 7) | def edit
method update (line 12) | def update
method set_card (line 22) | def set_card
FILE: app/controllers/cards/closures_controller.rb
class Cards::ClosuresController (line 1) | class Cards::ClosuresController < ApplicationController
method create (line 4) | def create
method destroy (line 15) | def destroy
method refresh_stream_after_reopen (line 26) | def refresh_stream_after_reopen
FILE: app/controllers/cards/columns_controller.rb
class Cards::ColumnsController (line 1) | class Cards::ColumnsController < ApplicationController
method edit (line 2) | def edit
FILE: app/controllers/cards/comments/reactions_controller.rb
class Cards::Comments::ReactionsController (line 1) | class Cards::Comments::ReactionsController < ApplicationController
method index (line 14) | def index
method new (line 18) | def new
method create (line 22) | def create
method destroy (line 31) | def destroy
method set_comment (line 41) | def set_comment
method set_reactable (line 45) | def set_reactable
method set_reaction (line 49) | def set_reaction
method ensure_permission_to_administer_reaction (line 53) | def ensure_permission_to_administer_reaction
FILE: app/controllers/cards/comments_controller.rb
class Cards::CommentsController (line 1) | class Cards::CommentsController < ApplicationController
method index (line 9) | def index
method create (line 13) | def create
method show (line 22) | def show
method edit (line 25) | def edit
method update (line 28) | def update
method destroy (line 37) | def destroy
method set_comment (line 47) | def set_comment
method ensure_creatorship (line 51) | def ensure_creatorship
method ensure_card_is_commentable (line 55) | def ensure_card_is_commentable
method comment_params (line 59) | def comment_params
FILE: app/controllers/cards/drafts_controller.rb
class Cards::DraftsController (line 1) | class Cards::DraftsController < ApplicationController
method show (line 6) | def show
method redirect_if_published (line 10) | def redirect_if_published
FILE: app/controllers/cards/goldnesses_controller.rb
class Cards::GoldnessesController (line 1) | class Cards::GoldnessesController < ApplicationController
method create (line 4) | def create
method destroy (line 13) | def destroy
FILE: app/controllers/cards/images_controller.rb
class Cards::ImagesController (line 1) | class Cards::ImagesController < ApplicationController
method destroy (line 4) | def destroy
FILE: app/controllers/cards/not_nows_controller.rb
class Cards::NotNowsController (line 1) | class Cards::NotNowsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/cards/pins_controller.rb
class Cards::PinsController (line 1) | class Cards::PinsController < ApplicationController
method show (line 4) | def show
method create (line 8) | def create
method destroy (line 19) | def destroy
method broadcast_add_pin_to_tray (line 31) | def broadcast_add_pin_to_tray
method broadcast_remove_pin_from_tray (line 35) | def broadcast_remove_pin_from_tray
method render_pin_button_replacement (line 39) | def render_pin_button_replacement
FILE: app/controllers/cards/previews_controller.rb
class Cards::PreviewsController (line 1) | class Cards::PreviewsController < ApplicationController
method index (line 6) | def index
FILE: app/controllers/cards/publishes_controller.rb
class Cards::PublishesController (line 1) | class Cards::PublishesController < ApplicationController
method create (line 4) | def create
method add_another_param? (line 22) | def add_another_param?
FILE: app/controllers/cards/reactions_controller.rb
class Cards::ReactionsController (line 1) | class Cards::ReactionsController < ApplicationController
method index (line 13) | def index
method new (line 17) | def new
method create (line 21) | def create
method destroy (line 30) | def destroy
method set_reactable (line 40) | def set_reactable
method set_reaction (line 44) | def set_reaction
method ensure_permission_to_administer_reaction (line 48) | def ensure_permission_to_administer_reaction
FILE: app/controllers/cards/readings_controller.rb
class Cards::ReadingsController (line 1) | class Cards::ReadingsController < ApplicationController
method create (line 4) | def create
method destroy (line 14) | def destroy
method record_board_access (line 25) | def record_board_access
FILE: app/controllers/cards/self_assignments_controller.rb
class Cards::SelfAssignmentsController (line 1) | class Cards::SelfAssignmentsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/cards/steps_controller.rb
class Cards::StepsController (line 1) | class Cards::StepsController < ApplicationController
method index (line 8) | def index
method create (line 12) | def create
method show (line 21) | def show
method edit (line 24) | def edit
method update (line 27) | def update
method destroy (line 36) | def destroy
method set_step (line 46) | def set_step
method step_params (line 50) | def step_params
FILE: app/controllers/cards/taggings_controller.rb
class Cards::TaggingsController (line 1) | class Cards::TaggingsController < ApplicationController
method new (line 4) | def new
method create (line 10) | def create
method sanitized_tag_title_param (line 20) | def sanitized_tag_title_param
FILE: app/controllers/cards/triages_controller.rb
class Cards::TriagesController (line 1) | class Cards::TriagesController < ApplicationController
method create (line 4) | def create
method destroy (line 14) | def destroy
FILE: app/controllers/cards/watches_controller.rb
class Cards::WatchesController (line 1) | class Cards::WatchesController < ApplicationController
method show (line 4) | def show
method create (line 8) | def create
method destroy (line 17) | def destroy
FILE: app/controllers/cards_controller.rb
class CardsController (line 1) | class CardsController < ApplicationController
method index (line 11) | def index
method create (line 15) | def create
method show (line 29) | def show
method edit (line 32) | def edit
method update (line 35) | def update
method destroy (line 44) | def destroy
method set_board (line 54) | def set_board
method set_card (line 58) | def set_card
method redirect_if_drafted (line 62) | def redirect_if_drafted
method ensure_permission_to_administer_card (line 66) | def ensure_permission_to_administer_card
method card_params (line 70) | def card_params
FILE: app/controllers/client_configurations_controller.rb
class ClientConfigurationsController (line 1) | class ClientConfigurationsController < ApplicationController
method show (line 5) | def show
method client_configuration_name (line 12) | def client_configuration_name
FILE: app/controllers/columns/cards/drops/closures_controller.rb
class Columns::Cards::Drops::ClosuresController (line 1) | class Columns::Cards::Drops::ClosuresController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/columns/cards/drops/columns_controller.rb
class Columns::Cards::Drops::ColumnsController (line 1) | class Columns::Cards::Drops::ColumnsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/columns/cards/drops/not_nows_controller.rb
class Columns::Cards::Drops::NotNowsController (line 1) | class Columns::Cards::Drops::NotNowsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/columns/cards/drops/streams_controller.rb
class Columns::Cards::Drops::StreamsController (line 1) | class Columns::Cards::Drops::StreamsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/columns/left_positions_controller.rb
class Columns::LeftPositionsController (line 1) | class Columns::LeftPositionsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/columns/right_positions_controller.rb
class Columns::RightPositionsController (line 1) | class Columns::RightPositionsController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/concerns/authentication.rb
type Authentication (line 1) | module Authentication
function require_unauthenticated_access (line 16) | def require_unauthenticated_access(**options)
function allow_unauthenticated_access (line 21) | def allow_unauthenticated_access(**options)
function disallow_account_scope (line 27) | def disallow_account_scope(**options)
function authenticated? (line 34) | def authenticated?
function require_account (line 38) | def require_account
function require_authentication (line 44) | def require_authentication
function resume_session (line 48) | def resume_session
function find_session_by_cookie (line 54) | def find_session_by_cookie
function authenticate_by_bearer_token (line 58) | def authenticate_by_bearer_token
function request_authentication (line 68) | def request_authentication
function after_authentication_url (line 76) | def after_authentication_url
function redirect_authenticated_user (line 80) | def redirect_authenticated_user
function redirect_tenanted_request (line 84) | def redirect_tenanted_request
function start_new_session_for (line 88) | def start_new_session_for(identity)
function set_current_session (line 94) | def set_current_session(session)
function terminate_session (line 99) | def terminate_session
function session_token (line 104) | def session_token
FILE: app/controllers/concerns/authentication/via_magic_link.rb
type Authentication::ViaMagicLink (line 1) | module Authentication::ViaMagicLink
function ensure_development_magic_link_not_leaked (line 9) | def ensure_development_magic_link_not_leaked
function redirect_to_fake_session_magic_link (line 15) | def redirect_to_fake_session_magic_link(email_address, **options)
function redirect_to_session_magic_link (line 25) | def redirect_to_session_magic_link(magic_link, return_to: nil)
function serve_development_magic_link (line 36) | def serve_development_magic_link(magic_link)
function set_pending_authentication_token (line 43) | def set_pending_authentication_token(magic_link)
function email_address_pending_authentication (line 52) | def email_address_pending_authentication
function pending_authentication_token_verifier (line 56) | def pending_authentication_token_verifier
function pending_authentication_token (line 60) | def pending_authentication_token
function clear_pending_authentication_token (line 64) | def clear_pending_authentication_token
FILE: app/controllers/concerns/authorization.rb
type Authorization (line 1) | module Authorization
function allow_unauthorized_access (line 9) | def allow_unauthorized_access(**options)
function require_access_without_a_user (line 13) | def require_access_without_a_user(**options)
function ensure_admin (line 20) | def ensure_admin
function ensure_staff (line 24) | def ensure_staff
function authenticated_account_access? (line 28) | def authenticated_account_access?
function ensure_can_access_account (line 32) | def ensure_can_access_account
function redirect_existing_user (line 41) | def redirect_existing_user
FILE: app/controllers/concerns/block_search_engine_indexing.rb
type BlockSearchEngineIndexing (line 3) | module BlockSearchEngineIndexing
function block_search_engine_indexing (line 11) | def block_search_engine_indexing
FILE: app/controllers/concerns/board_scoped.rb
type BoardScoped (line 1) | module BoardScoped
function set_board (line 9) | def set_board
function ensure_permission_to_admin_board (line 13) | def ensure_permission_to_admin_board
FILE: app/controllers/concerns/card_scoped.rb
type CardScoped (line 1) | module CardScoped
function set_card (line 9) | def set_card
function set_board (line 13) | def set_board
function render_card_replacement (line 17) | def render_card_replacement
function capture_card_location (line 21) | def capture_card_location
function refresh_stream_if_needed (line 26) | def refresh_stream_if_needed
FILE: app/controllers/concerns/column_scoped.rb
type ColumnScoped (line 1) | module ColumnScoped
function set_column (line 9) | def set_column
FILE: app/controllers/concerns/current_request.rb
type CurrentRequest (line 1) | module CurrentRequest
FILE: app/controllers/concerns/current_timezone.rb
type CurrentTimezone (line 2) | module CurrentTimezone
function set_current_timezone (line 14) | def set_current_timezone(&)
function timezone_from_cookie (line 18) | def timezone_from_cookie
FILE: app/controllers/concerns/day_timelines_scoped.rb
type DayTimelinesScoped (line 1) | module DayTimelinesScoped
function set_day_timeline (line 11) | def set_day_timeline
function day (line 15) | def day
FILE: app/controllers/concerns/filter_scoped.rb
type FilterScoped (line 1) | module FilterScoped
function set_filter (line 10) | def set_filter
function filter_params (line 18) | def filter_params
function set_user_filtering (line 22) | def set_user_filtering
function expanded_param (line 26) | def expanded_param
FILE: app/controllers/concerns/request_forgery_protection.rb
type RequestForgeryProtection (line 1) | module RequestForgeryProtection
function verified_via_header_only? (line 9) | def verified_via_header_only?
function allowed_api_request? (line 13) | def allowed_api_request?
FILE: app/controllers/concerns/routing_headers.rb
type RoutingHeaders (line 1) | module RoutingHeaders
function set_target_header (line 9) | def set_target_header
FILE: app/controllers/concerns/set_platform.rb
type SetPlatform (line 1) | module SetPlatform
function platform (line 9) | def platform
FILE: app/controllers/concerns/turbo_flash.rb
type TurboFlash (line 1) | module TurboFlash
function turbo_stream_flash (line 9) | def turbo_stream_flash(**flash_options)
FILE: app/controllers/concerns/view_transitions.rb
type ViewTransitions (line 2) | module ViewTransitions
function disable_view_transitions (line 10) | def disable_view_transitions
function page_refresh? (line 14) | def page_refresh?
FILE: app/controllers/events/day_timeline/columns_controller.rb
class Events::DayTimeline::ColumnsController (line 1) | class Events::DayTimeline::ColumnsController < ApplicationController
method show (line 7) | def show
method ensure_valid_column (line 14) | def ensure_valid_column
method set_column (line 18) | def set_column
FILE: app/controllers/events/days_controller.rb
class Events::DaysController (line 1) | class Events::DaysController < ApplicationController
method index (line 4) | def index
FILE: app/controllers/events_controller.rb
class EventsController (line 1) | class EventsController < ApplicationController
method index (line 4) | def index
FILE: app/controllers/filters/settings_refreshes_controller.rb
class Filters::SettingsRefreshesController (line 1) | class Filters::SettingsRefreshesController < ApplicationController
method create (line 4) | def create
FILE: app/controllers/filters_controller.rb
class FiltersController (line 1) | class FiltersController < ApplicationController
method create (line 4) | def create
method destroy (line 8) | def destroy
method set_filters (line 14) | def set_filters
method filter_params (line 18) | def filter_params
FILE: app/controllers/join_codes_controller.rb
class JoinCodesController (line 1) | class JoinCodesController < ApplicationController
method new (line 11) | def new
method create (line 14) | def create
method set_identity (line 32) | def set_identity
method set_join_code (line 44) | def set_join_code
method ensure_join_code_is_valid (line 48) | def ensure_join_code_is_valid
FILE: app/controllers/landings_controller.rb
class LandingsController (line 1) | class LandingsController < ApplicationController
method show (line 2) | def show
FILE: app/controllers/my/access_tokens_controller.rb
class My::AccessTokensController (line 1) | class My::AccessTokensController < ApplicationController
method index (line 6) | def index
method show (line 10) | def show
method new (line 16) | def new
method create (line 20) | def create
method destroy (line 37) | def destroy
method my_access_tokens (line 47) | def my_access_tokens
method access_token_params (line 51) | def access_token_params
method verifier (line 55) | def verifier
FILE: app/controllers/my/identities_controller.rb
class My::IdentitiesController (line 1) | class My::IdentitiesController < ApplicationController
method show (line 4) | def show
FILE: app/controllers/my/menus_controller.rb
class My::MenusController (line 1) | class My::MenusController < ApplicationController
method show (line 2) | def show
FILE: app/controllers/my/passkey_challenges_controller.rb
class My::PasskeyChallengesController (line 1) | class My::PasskeyChallengesController < ActionPack::Passkey::ChallengesC...
FILE: app/controllers/my/passkeys_controller.rb
class My::PasskeysController (line 1) | class My::PasskeysController < ApplicationController
method index (line 6) | def index
method create (line 11) | def create
method edit (line 17) | def edit
method update (line 20) | def update
method destroy (line 25) | def destroy
method set_passkey (line 31) | def set_passkey
FILE: app/controllers/my/pins_controller.rb
class My::PinsController (line 1) | class My::PinsController < ApplicationController
method index (line 2) | def index
method user_pins (line 8) | def user_pins
method pins_limit (line 12) | def pins_limit
FILE: app/controllers/my/timezones_controller.rb
class My::TimezonesController (line 1) | class My::TimezonesController < ApplicationController
method update (line 2) | def update
method timezone_param (line 7) | def timezone_param
FILE: app/controllers/notifications/bulk_readings_controller.rb
class Notifications::BulkReadingsController (line 1) | class Notifications::BulkReadingsController < ApplicationController
method create (line 2) | def create
method from_tray? (line 18) | def from_tray?
FILE: app/controllers/notifications/readings_controller.rb
class Notifications::ReadingsController (line 1) | class Notifications::ReadingsController < ApplicationController
method create (line 2) | def create
method destroy (line 12) | def destroy
FILE: app/controllers/notifications/settings_controller.rb
class Notifications::SettingsController (line 1) | class Notifications::SettingsController < ApplicationController
method show (line 6) | def show
method update (line 10) | def update
method set_settings (line 20) | def set_settings
method settings_params (line 24) | def settings_params
FILE: app/controllers/notifications/trays_controller.rb
class Notifications::TraysController (line 1) | class Notifications::TraysController < ApplicationController
method show (line 4) | def show
method unread_notifications (line 16) | def unread_notifications
method read_notifications (line 20) | def read_notifications
method include_read? (line 24) | def include_read?
FILE: app/controllers/notifications/unsubscribes_controller.rb
class Notifications::UnsubscribesController (line 1) | class Notifications::UnsubscribesController < ApplicationController
method new (line 7) | def new
method create (line 10) | def create
method show (line 15) | def show
method set_user (line 19) | def set_user
FILE: app/controllers/notifications_controller.rb
class NotificationsController (line 1) | class NotificationsController < ApplicationController
method index (line 5) | def index
method max_unread_notifications (line 17) | def max_unread_notifications
FILE: app/controllers/prompts/boards/users_controller.rb
class Prompts::Boards::UsersController (line 1) | class Prompts::Boards::UsersController < ApplicationController
method index (line 4) | def index
FILE: app/controllers/prompts/cards_controller.rb
class Prompts::CardsController (line 1) | class Prompts::CardsController < ApplicationController
method index (line 4) | def index
method filter_param (line 17) | def filter_param
method search_cards (line 21) | def search_cards
method published_cards (line 28) | def published_cards
method prepending_exact_matches_by_id (line 32) | def prepending_exact_matches_by_id(cards)
FILE: app/controllers/prompts/tags_controller.rb
class Prompts::TagsController (line 1) | class Prompts::TagsController < ApplicationController
method index (line 2) | def index
FILE: app/controllers/prompts/users_controller.rb
class Prompts::UsersController (line 1) | class Prompts::UsersController < ApplicationController
method index (line 2) | def index
FILE: app/controllers/public/base_controller.rb
class Public::BaseController (line 1) | class Public::BaseController < ApplicationController
method set_board (line 10) | def set_board
method set_card (line 14) | def set_card
method set_public_cache_expiration (line 18) | def set_public_cache_expiration
method ensure_board_accessible (line 22) | def ensure_board_accessible
FILE: app/controllers/public/boards/columns/closeds_controller.rb
class Public::Boards::Columns::ClosedsController (line 1) | class Public::Boards::Columns::ClosedsController < Public::BaseController
method show (line 2) | def show
FILE: app/controllers/public/boards/columns/not_nows_controller.rb
class Public::Boards::Columns::NotNowsController (line 1) | class Public::Boards::Columns::NotNowsController < Public::BaseController
method show (line 2) | def show
FILE: app/controllers/public/boards/columns/streams_controller.rb
class Public::Boards::Columns::StreamsController (line 1) | class Public::Boards::Columns::StreamsController < Public::BaseController
method show (line 2) | def show
FILE: app/controllers/public/boards/columns_controller.rb
class Public::Boards::ColumnsController (line 1) | class Public::Boards::ColumnsController < Public::BaseController
method show (line 4) | def show
method set_card (line 10) | def set_card
method set_column (line 13) | def set_column
FILE: app/controllers/public/boards_controller.rb
class Public::BoardsController (line 1) | class Public::BoardsController < Public::BaseController
method show (line 2) | def show
FILE: app/controllers/public/cards_controller.rb
class Public::CardsController (line 1) | class Public::CardsController < Public::BaseController
method show (line 2) | def show
FILE: app/controllers/pwa_controller.rb
class PwaController (line 1) | class PwaController < ApplicationController
method service_worker (line 6) | def service_worker
FILE: app/controllers/qr_codes_controller.rb
class QrCodesController (line 1) | class QrCodesController < ApplicationController
method show (line 4) | def show
FILE: app/controllers/searches/queries_controller.rb
class Searches::QueriesController (line 1) | class Searches::QueriesController < ApplicationController
method create (line 2) | def create
FILE: app/controllers/searches_controller.rb
class SearchesController (line 1) | class SearchesController < ApplicationController
method show (line 4) | def show
FILE: app/controllers/sessions/magic_links_controller.rb
class Sessions::MagicLinksController (line 1) | class Sessions::MagicLinksController < ApplicationController
method show (line 9) | def show
method create (line 12) | def create
method ensure_that_email_address_pending_authentication_exists (line 21) | def ensure_that_email_address_pending_authentication_exists
method code (line 31) | def code
method authenticate (line 35) | def authenticate(magic_link)
method sign_in (line 43) | def sign_in(magic_link)
method email_address_mismatch (line 53) | def email_address_mismatch
method invalid_code (line 63) | def invalid_code
method after_sign_in_url (line 70) | def after_sign_in_url(magic_link)
method rate_limit_exceeded (line 78) | def rate_limit_exceeded
method requires_signup_completion? (line 86) | def requires_signup_completion?(magic_link)
FILE: app/controllers/sessions/menus_controller.rb
class Sessions::MenusController (line 1) | class Sessions::MenusController < ApplicationController
method show (line 6) | def show
FILE: app/controllers/sessions/passkeys_controller.rb
class Sessions::PasskeysController (line 1) | class Sessions::PasskeysController < ApplicationController
method create (line 8) | def create
method rate_limit_exceeded (line 25) | def rate_limit_exceeded
FILE: app/controllers/sessions/transfers_controller.rb
class Sessions::TransfersController (line 1) | class Sessions::TransfersController < ApplicationController
method show (line 5) | def show
method update (line 8) | def update
FILE: app/controllers/sessions_controller.rb
class SessionsController (line 1) | class SessionsController < ApplicationController
method new (line 10) | def new
method create (line 14) | def create
method destroy (line 24) | def destroy
method magic_link_from_sign_in_or_sign_up (line 34) | def magic_link_from_sign_in_or_sign_up
method email_address (line 43) | def email_address
method rate_limit_exceeded (line 47) | def rate_limit_exceeded
method sign_in (line 56) | def sign_in(identity)
method sign_up (line 60) | def sign_up
FILE: app/controllers/signups/completions_controller.rb
class Signups::CompletionsController (line 1) | class Signups::CompletionsController < ApplicationController
method new (line 8) | def new
method create (line 12) | def create
method signup_params (line 23) | def signup_params
method welcome_to_account (line 27) | def welcome_to_account
method invalid_signup (line 38) | def invalid_signup
FILE: app/controllers/signups_controller.rb
class SignupsController (line 1) | class SignupsController < ApplicationController
method new (line 12) | def new
method create (line 16) | def create
method redirect_authenticated_user (line 26) | def redirect_authenticated_user
method enforce_tenant_limit (line 30) | def enforce_tenant_limit
method signup_params (line 34) | def signup_params
FILE: app/controllers/tags_controller.rb
class TagsController (line 1) | class TagsController < ApplicationController
method index (line 2) | def index
FILE: app/controllers/users/avatars_controller.rb
class Users::AvatarsController (line 1) | class Users::AvatarsController < ApplicationController
method show (line 7) | def show
method destroy (line 17) | def destroy
method set_user (line 27) | def set_user
method ensure_permission_to_administer_user (line 31) | def ensure_permission_to_administer_user
method cache_control (line 35) | def cache_control
method render_initials (line 43) | def render_initials
FILE: app/controllers/users/data_exports_controller.rb
class Users::DataExportsController (line 1) | class Users::DataExportsController < ApplicationController
method show (line 9) | def show
method create (line 12) | def create
method set_user (line 18) | def set_user
method ensure_current_user (line 22) | def ensure_current_user
method ensure_export_limit_not_exceeded (line 26) | def ensure_export_limit_not_exceeded
method set_export (line 30) | def set_export
FILE: app/controllers/users/email_addresses/confirmations_controller.rb
class Users::EmailAddresses::ConfirmationsController (line 1) | class Users::EmailAddresses::ConfirmationsController < ApplicationContro...
method show (line 7) | def show
method create (line 10) | def create
method set_user (line 22) | def set_user
method token (line 26) | def token
FILE: app/controllers/users/email_addresses_controller.rb
class Users::EmailAddressesController (line 1) | class Users::EmailAddressesController < ApplicationController
method new (line 5) | def new
method create (line 8) | def create
method set_user (line 20) | def set_user
method new_email_address (line 24) | def new_email_address
FILE: app/controllers/users/events_controller.rb
class Users::EventsController (line 1) | class Users::EventsController < ApplicationController
method show (line 6) | def show
method set_user (line 14) | def set_user
method day_param (line 18) | def day_param
FILE: app/controllers/users/joins_controller.rb
class Users::JoinsController (line 1) | class Users::JoinsController < ApplicationController
method new (line 6) | def new
method create (line 9) | def create
method user_params (line 19) | def user_params
FILE: app/controllers/users/push_subscriptions_controller.rb
class Users::PushSubscriptionsController (line 1) | class Users::PushSubscriptionsController < ApplicationController
method index (line 6) | def index
method create (line 9) | def create
method destroy (line 18) | def destroy
method set_push_subscriptions (line 28) | def set_push_subscriptions
method push_subscription_params (line 32) | def push_subscription_params
FILE: app/controllers/users/roles_controller.rb
class Users::RolesController (line 1) | class Users::RolesController < ApplicationController
method update (line 7) | def update
method set_user (line 17) | def set_user
method ensure_permission_to_administer_user (line 21) | def ensure_permission_to_administer_user
method role_params (line 25) | def role_params
FILE: app/controllers/users/verifications_controller.rb
class Users::VerificationsController (line 1) | class Users::VerificationsController < ApplicationController
method new (line 4) | def new
method create (line 7) | def create
FILE: app/controllers/users_controller.rb
class UsersController (line 1) | class UsersController < ApplicationController
method index (line 7) | def index
method show (line 11) | def show
method edit (line 14) | def edit
method update (line 17) | def update
method destroy (line 31) | def destroy
method set_user (line 41) | def set_user
method ensure_permission_to_change_user (line 45) | def ensure_permission_to_change_user
method user_params (line 49) | def user_params
FILE: app/controllers/webhooks/activations_controller.rb
class Webhooks::ActivationsController (line 1) | class Webhooks::ActivationsController < ApplicationController
method create (line 6) | def create
FILE: app/controllers/webhooks_controller.rb
class WebhooksController (line 1) | class WebhooksController < ApplicationController
method index (line 9) | def index
method show (line 13) | def show
method new (line 16) | def new
method create (line 20) | def create
method edit (line 36) | def edit
method update (line 39) | def update
method destroy (line 53) | def destroy
method set_webhook (line 63) | def set_webhook
method webhook_params (line 67) | def webhook_params
FILE: app/helpers/accesses_helper.rb
function access_menu_tag (line 2) | def access_menu_tag(board, **options, &)
FILE: app/helpers/application_helper.rb
type ApplicationHelper (line 1) | module ApplicationHelper
function page_title_tag (line 2) | def page_title_tag
function icon_tag (line 9) | def icon_tag(name, **options)
function back_link_to (line 13) | def back_link_to(label, url, action, prefer_referrer: [], **options)
FILE: app/helpers/avatars_helper.rb
type AvatarsHelper (line 1) | module AvatarsHelper
function avatar_background_color (line 2) | def avatar_background_color(user)
function avatar_tag (line 6) | def avatar_tag(user, hidden_for_screen_reader: false, **options)
function avatar_tags (line 15) | def avatar_tags(users, **options)
function mail_avatar_tag (line 19) | def mail_avatar_tag(user, size: 48, **options)
function avatar_preview_tag (line 29) | def avatar_preview_tag(user, hidden_for_screen_reader: false, **options)
function avatar_image_tag (line 37) | def avatar_image_tag(user, **options)
FILE: app/helpers/boards_helper.rb
type BoardsHelper (line 1) | module BoardsHelper
function link_back_to_board (line 2) | def link_back_to_board(board, prefer_referrer: [])
function link_to_edit_board (line 6) | def link_to_edit_board(board)
FILE: app/helpers/bridge_helper.rb
type BridgeHelper (line 1) | module BridgeHelper
function bridge_icon (line 2) | def bridge_icon(name)
function bridged_button_to_board (line 6) | def bridged_button_to_board(board)
function bridged_share_url_button (line 14) | def bridged_share_url_button(description = nil)
function bridge_share_card_description (line 24) | def bridge_share_card_description(card)
function bridge_share_board_description (line 32) | def bridge_share_board_description(board)
FILE: app/helpers/cards_helper.rb
type CardsHelper (line 1) | module CardsHelper
function card_article_tag (line 2) | def card_article_tag(card, id: dom_id(card, :article), data: {}, **opt...
function card_title_tag (line 21) | def card_title_tag(card)
function card_drafted_or_added (line 31) | def card_drafted_or_added(card)
function card_social_tags (line 35) | def card_social_tags(card)
function button_to_remove_card_image (line 42) | def button_to_remove_card_image(card)
FILE: app/helpers/clipboard_helper.rb
type ClipboardHelper (line 1) | module ClipboardHelper
function button_to_copy_to_clipboard (line 2) | def button_to_copy_to_clipboard(url, &)
FILE: app/helpers/columns_helper.rb
type ColumnsHelper (line 1) | module ColumnsHelper
function button_to_set_column (line 2) | def button_to_set_column(card, column)
function column_tag (line 14) | def column_tag(id:, name:, drop_url:, collapsed: true, selected: nil, ...
function column_frame_tag (line 48) | def column_frame_tag(id, src: nil, data: {}, **options, &block)
FILE: app/helpers/comments_helper.rb
type CommentsHelper (line 1) | module CommentsHelper
function new_comment_placeholder (line 2) | def new_comment_placeholder(card)
FILE: app/helpers/emoji_helper.rb
type EmojiHelper (line 1) | module EmojiHelper
FILE: app/helpers/entropy_helper.rb
type EntropyHelper (line 1) | module EntropyHelper
function entropy_bubble_options_for (line 2) | def entropy_bubble_options_for(card)
function stalled_bubble_options_for (line 10) | def stalled_bubble_options_for(card)
FILE: app/helpers/events_helper.rb
type EventsHelper (line 1) | module EventsHelper
function event_action_icon (line 2) | def event_action_icon(event)
function events_at_hour_container (line 19) | def events_at_hour_container(column, hour, &block)
FILE: app/helpers/excerpt_helper.rb
type ExcerptHelper (line 1) | module ExcerptHelper
function format_excerpt (line 2) | def format_excerpt(content, length: 200)
FILE: app/helpers/filters_helper.rb
type FiltersHelper (line 1) | module FiltersHelper
function filter_chip_tag (line 2) | def filter_chip_tag(text, params)
function filter_hidden_field_tag (line 9) | def filter_hidden_field_tag(key, value)
function filter_selected_boards_title (line 14) | def filter_selected_boards_title(user_filtering)
function filter_place_menu_item (line 18) | def filter_place_menu_item(path, label, icon, new_window: false, curre...
function filter_dialog (line 32) | def filter_dialog(label, &block)
function filter_title (line 43) | def filter_title(title)
function collapsible_nav_section (line 47) | def collapsible_nav_section(title, **properties, &block)
function filter_hotkey_link (line 59) | def filter_hotkey_link(title, path, key, icon)
function sorted_by_label (line 67) | def sorted_by_label(sort_value)
FILE: app/helpers/forms_helper.rb
type FormsHelper (line 1) | module FormsHelper
function auto_submit_form_with (line 2) | def auto_submit_form_with(**attributes, &)
function bridged_form_with (line 13) | def bridged_form_with(**attributes, &)
FILE: app/helpers/hotkeys_helper.rb
type HotkeysHelper (line 1) | module HotkeysHelper
function hotkey_label (line 3) | def hotkey_label(hotkey)
FILE: app/helpers/html_helper.rb
type HtmlHelper (line 1) | module HtmlHelper
function format_html (line 2) | def format_html(html)
function card_html_title (line 6) | def card_html_title(card)
FILE: app/helpers/login_helper.rb
type LoginHelper (line 1) | module LoginHelper
function login_url (line 2) | def login_url
function logout_url (line 6) | def logout_url
function redirect_to_login_url (line 10) | def redirect_to_login_url
function redirect_to_logout_url (line 14) | def redirect_to_logout_url
FILE: app/helpers/messages_helper.rb
type MessagesHelper (line 1) | module MessagesHelper
function messages_tag (line 2) | def messages_tag(card, &)
FILE: app/helpers/my/menu_helper.rb
type My::MenuHelper (line 1) | module My::MenuHelper
function jump_field_tag (line 2) | def jump_field_tag
function my_menu_board_item (line 21) | def my_menu_board_item(board)
function my_menu_tag_item (line 27) | def my_menu_tag_item(the_tag)
function my_menu_user_item (line 35) | def my_menu_user_item(user)
function my_menu_filter_item (line 41) | def my_menu_filter_item(filter)
function my_menu_item (line 52) | def my_menu_item(item, record)
FILE: app/helpers/notifications_helper.rb
type NotificationsHelper (line 1) | module NotificationsHelper
function event_notification_title (line 2) | def event_notification_title(event)
function event_notification_body (line 9) | def event_notification_body(event)
function notification_tag (line 29) | def notification_tag(notification, &)
function notification_toggle_read_button (line 42) | def notification_toggle_read_button(notification, url:)
function notifications_next_page_link (line 64) | def notifications_next_page_link(page)
function bundle_email_frequency_options_for (line 70) | def bundle_email_frequency_options_for(settings)
function event_notification_action (line 80) | def event_notification_action(event)
function comment_notification_body (line 88) | def comment_notification_body(event)
function card_notification_title (line 93) | def card_notification_title(card)
FILE: app/helpers/pagination_helper.rb
function link_to_next_page (line 6) | def link_to_next_page(namespace, page, activate_when_observed: false, la...
function pagination_link (line 13) | def pagination_link(namespace, page_number, activate_when_observed: fals...
function pagination_frame_id_for (line 27) | def pagination_frame_id_for(namespace, page_number)
function with_manual_pagination (line 31) | def with_manual_pagination(name, page, **properties)
function with_automatic_pagination (line 40) | def with_automatic_pagination(name, page, **properties)
function day_timeline_pagination_frame_tag (line 49) | def day_timeline_pagination_frame_tag(day_timeline, &)
FILE: app/helpers/qr_codes_helper.rb
type QrCodesHelper (line 1) | module QrCodesHelper
function qr_code_image (line 2) | def qr_code_image(url)
FILE: app/helpers/reactions_helper.rb
type ReactionsHelper (line 1) | module ReactionsHelper
function reaction_path_prefix_for (line 2) | def reaction_path_prefix_for(reactable)
FILE: app/helpers/rich_text_helper.rb
type RichTextHelper (line 1) | module RichTextHelper
function mentions_prompt (line 2) | def mentions_prompt(board)
function global_mentions_prompt (line 6) | def global_mentions_prompt
function tags_prompt (line 10) | def tags_prompt
function cards_prompt (line 14) | def cards_prompt
function general_prompts (line 18) | def general_prompts(board)
FILE: app/helpers/tenanting_helper.rb
type TenantingHelper (line 1) | module TenantingHelper
function tenanted_action_cable_meta_tag (line 2) | def tenanted_action_cable_meta_tag
FILE: app/helpers/time_helper.rb
type TimeHelper (line 1) | module TimeHelper
function local_datetime_tag (line 2) | def local_datetime_tag(datetime, style: :time, **attributes)
FILE: app/helpers/users_helper.rb
type UsersHelper (line 1) | module UsersHelper
function role_display_name (line 2) | def role_display_name(user)
FILE: app/helpers/webhooks_helper.rb
type WebhooksHelper (line 1) | module WebhooksHelper
function webhook_action_options (line 17) | def webhook_action_options(actions = Webhook::PERMITTED_ACTIONS)
function webhook_action_label (line 21) | def webhook_action_label(action)
function link_to_webhooks (line 25) | def link_to_webhooks(board, &)
FILE: app/javascript/controllers/assignment_limit_controller.js
method connect (line 7) | connect() {
method countValueChanged (line 11) | countValueChanged() {
method updateState (line 15) | updateState() {
FILE: app/javascript/controllers/auto_click_controller.js
method connect (line 4) | connect() {
FILE: app/javascript/controllers/auto_save_controller.js
constant AUTOSAVE_INTERVAL (line 4) | const AUTOSAVE_INTERVAL = 3000
method disconnect (line 11) | disconnect() {
method submit (line 17) | async submit() {
method change (line 23) | change(event) {
method #scheduleSave (line 31) | #scheduleSave() {
method #save (line 35) | async #save() {
method #resetTimer (line 40) | #resetTimer() {
method #dirty (line 45) | get #dirty() {
FILE: app/javascript/controllers/auto_submit_controller.js
method connect (line 4) | connect() {
method submit (line 9) | submit() {
method #handleSubmitEnd (line 15) | #handleSubmitEnd(event) {
method #markAsBusy (line 24) | #markAsBusy() {
method #clearBusy (line 28) | #clearBusy() {
method #disableSubmit (line 32) | #disableSubmit() {
method #enableSubmit (line 36) | #enableSubmit() {
method #submitElements (line 40) | #submitElements() {
FILE: app/javascript/controllers/autoresize_controller.js
method connect (line 6) | connect() {
method resize (line 10) | resize() {
FILE: app/javascript/controllers/badge_controller.js
method connect (line 8) | connect() {
method update (line 12) | update() {
method clear (line 26) | clear() {
method #unreadCount (line 34) | get #unreadCount() {
method #available (line 38) | get #available() {
FILE: app/javascript/controllers/bar_controller.js
method dialogOutletConnected (line 12) | dialogOutletConnected(outlet, element) {
method reset (line 17) | reset() {
method showModalAndSubmit (line 25) | showModalAndSubmit(event) {
method showModal (line 31) | showModal() {
method search (line 35) | search(event) {
method #restoreFocusAfterTurboFrameLoads (line 46) | #restoreFocusAfterTurboFrameLoads() {
method #loadTurboFrame (line 52) | #loadTurboFrame() {
method #clearTurboFrame (line 56) | #clearTurboFrame() {
method #showItem (line 61) | async #showItem(element) {
method #hideItem (line 71) | #hideItem(element) {
FILE: app/javascript/controllers/beacon_controller.js
method connect (line 7) | connect() {
method disconnect (line 13) | disconnect() {
method #sendBeacon (line 18) | #sendBeacon() {
FILE: app/javascript/controllers/boards_form_controller.js
method submitWithWarning (line 8) | async submitWithWarning(event) {
FILE: app/javascript/controllers/bridge/buttons_controller.js
method connect (line 8) | connect() {
method disconnect (line 18) | disconnect() {
method buttonTargetConnected (line 28) | buttonTargetConnected() {
method buttonTargetDisconnected (line 32) | buttonTargetDisconnected() {
method notifyBridgeOfConnect (line 38) | notifyBridgeOfConnect() {
method notifyBridgeOfDisconnect (line 50) | notifyBridgeOfDisconnect() {
method handleBeforeUnload (line 54) | handleBeforeUnload() {
method #clickButton (line 58) | #clickButton(message) {
method #enabledButtonTargets (line 63) | get #enabledButtonTargets() {
method #isControllerTearingDown (line 68) | #isControllerTearingDown() {
FILE: app/javascript/controllers/bridge/form_controller.js
method connect (line 9) | connect() {
method disconnect (line 19) | disconnect() {
method submitTargetConnected (line 27) | submitTargetConnected() {
method submitTargetDisconnected (line 32) | submitTargetDisconnected() {
method notifyBridgeOfConnect (line 37) | notifyBridgeOfConnect() {
method receive (line 47) | receive(message) {
method notifyBridgeOfDisconnect (line 58) | notifyBridgeOfDisconnect() {
method submitStart (line 62) | submitStart() {
method submitEnd (line 66) | submitEnd() {
method handleBeforeUnload (line 70) | handleBeforeUnload() {
method #observeSubmitTarget (line 74) | #observeSubmitTarget() {
FILE: app/javascript/controllers/bridge/insets_controller.js
method connect (line 8) | connect() {
method disconnect (line 13) | disconnect() {
method notifyBridgeOfConnect (line 18) | notifyBridgeOfConnect() {
method #setInsets (line 24) | #setInsets({ top, right, bottom, left }) {
FILE: app/javascript/controllers/bridge/overflow_menu_controller.js
method itemTargetConnected (line 8) | itemTargetConnected() {
method itemTargetDisconnected (line 12) | itemTargetDisconnected() {
method notifyBridgeOfConnect (line 18) | notifyBridgeOfConnect() {
method #clickItem (line 30) | #clickItem(message) {
method #enabledItemTargets (line 35) | get #enabledItemTargets() {
method #isControllerTearingDown (line 40) | #isControllerTearingDown() {
FILE: app/javascript/controllers/bridge/share_controller.js
method shareUrl (line 6) | shareUrl() {
FILE: app/javascript/controllers/bridge/stamp_controller.js
method connect (line 7) | connect() {
method disconnect (line 16) | disconnect() {
method notifyBridgeOfConnect (line 22) | notifyBridgeOfConnect() {
method notifyBridgeOfDisconnect (line 31) | notifyBridgeOfDisconnect() {
method #observeStamp (line 35) | #observeStamp() {
FILE: app/javascript/controllers/bridge/text_size_controller.js
method connect (line 6) | connect() {
method disconnect (line 11) | disconnect() {
method notifyBridgeOfConnect (line 16) | notifyBridgeOfConnect() {
method #setTextSize (line 22) | #setTextSize(data) {
FILE: app/javascript/controllers/bridge/title_controller.js
method connect (line 10) | async connect() {
method disconnect (line 17) | disconnect() {
method notifyBridgeOfVisibilityChange (line 23) | notifyBridgeOfVisibilityChange(visible) {
method #startObserver (line 29) | #startObserver() {
method #stopObserver (line 41) | #stopObserver() {
method #updateObserverIfNeeded (line 45) | #updateObserverIfNeeded() {
method #title (line 56) | get #title() {
method #topOffset (line 60) | get #topOffset() {
FILE: app/javascript/controllers/bubble_controller.js
constant REFRESH_INTERVAL (line 4) | const REFRESH_INTERVAL = 3_600_000 // 1 hour (in milliseconds)
method connect (line 12) | connect() {
method disconnect (line 17) | disconnect() {
method update (line 21) | update() {
method #hasEntropy (line 31) | get #hasEntropy() {
method #entropyCleanupInDays (line 35) | get #entropyCleanupInDays() {
method #showEntropy (line 39) | #showEntropy() {
method #render (line 47) | #render({ top, center, bottom }) {
method #isStalled (line 56) | get #isStalled() {
method #showStalled (line 62) | #showStalled() {
method #hide (line 70) | #hide() {
method #show (line 74) | #show() {
FILE: app/javascript/controllers/card_hotkeys_controller.js
method connect (line 7) | connect() {
method handleKeydown (line 12) | handleKeydown(event) {
method handleMorphComplete (line 22) | handleMorphComplete() {
method #shouldIgnore (line 32) | #shouldIgnore(event) {
method #hasModifier (line 40) | #hasModifier(event) {
method #selectedCard (line 44) | get #selectedCard() {
method #postponeCard (line 56) | async #postponeCard(event) {
method #closeCard (line 67) | async #closeCard(event) {
method #assignToMe (line 78) | async #assignToMe(event) {
method #performCardAction (line 89) | async #performCardAction(url, selection) {
method #hotkeysDisabled (line 125) | #hotkeysDisabled(navigableList) {
method "[" (line 130) | "["(event) { this.#postponeCard(event) }
method "]" (line 131) | "]"(event) { this.#closeCard(event) }
method m (line 132) | m(event) { this.#assignToMe(event) }
FILE: app/javascript/controllers/clear_offline_cache_controller.js
method clearCache (line 5) | clearCache() {
FILE: app/javascript/controllers/clicker_controller.js
method click (line 7) | async click() {
method #clickable (line 12) | get #clickable() {
FILE: app/javascript/controllers/collapsible_columns_controller.js
method initialize (line 13) | initialize() {
method connect (line 17) | async connect() {
method disconnect (line 26) | disconnect() {
method toggle (line 34) | toggle({ target }) {
method preventToggle (line 39) | preventToggle(event) {
method restoreState (line 45) | async restoreState(event) {
method focusOnColumn (line 50) | focusOnColumn({ target }) {
method frameColumnOnMobile (line 57) | frameColumnOnMobile(event) {
method #restoreColumnsDisablingTransitions (line 63) | async #restoreColumnsDisablingTransitions() {
method #disableTransitions (line 72) | #disableTransitions() {
method #enableTransitions (line 76) | #enableTransitions() {
method #toggleColumn (line 80) | #toggleColumn(column) {
method #collapseAllExcept (line 90) | #collapseAllExcept(clickedColumn) {
method #isCollapsed (line 100) | #isCollapsed(column) {
method #collapse (line 104) | #collapse(column) {
method #expand (line 113) | #expand({ column, saveState = true, scrollBehavior = "smooth" }) {
method #buttonFor (line 128) | #buttonFor(column) {
method #restoreColumns (line 132) | #restoreColumns() {
method #restoreColumn (line 138) | #restoreColumn(column) {
method #localStorageKeyFor (line 146) | #localStorageKeyFor(column) {
method #setupIntersectionObserver (line 150) | #setupIntersectionObserver() {
method #isDesktop (line 169) | get #isDesktop() {
method #handlePlatform (line 173) | #handlePlatform() {
method #handleDesktopMode (line 177) | async #handleDesktopMode() {
method #handleMobileMode (line 182) | #handleMobileMode() {
method #maybeButton (line 195) | get #maybeButton() {
FILE: app/javascript/controllers/combobox_controller.js
method connect (line 14) | connect() {
method change (line 18) | change(event) {
method #selectedLabel (line 25) | get #selectedLabel() {
method #selectedItem (line 35) | get #selectedItem() {
method #selectedItemValue (line 39) | #selectedItemValue() {
method #selectedItem (line 43) | set #selectedItem(item) {
method #clearSelection (line 54) | #clearSelection() {
method hiddenField (line 60) | get hiddenField() {
method #buildHiddenField (line 67) | #buildHiddenField() {
method #updateWithDefaultClass (line 73) | #updateWithDefaultClass() {
FILE: app/javascript/controllers/copy_to_clipboard_controller.js
method copy (line 7) | async copy(event) {
method reset (line 17) | reset() {
method #forceReflow (line 22) | #forceReflow() {
FILE: app/javascript/controllers/css_variable_counter_controller.js
method initialize (line 11) | initialize() {
method connect (line 15) | connect() {
method itemTargetConnected (line 21) | itemTargetConnected() {
method itemTargetDisconnected (line 25) | itemTargetDisconnected() {
FILE: app/javascript/controllers/details_controller.js
method close (line 6) | close() {
method closeOnClickOutside (line 10) | closeOnClickOutside({ target }) {
FILE: app/javascript/controllers/dialog_controller.js
method connect (line 14) | connect() {
method focusTouchTargetConnected (line 19) | focusTouchTargetConnected() {
method open (line 23) | open() {
method toggle (line 40) | toggle() {
method close (line 48) | close() {
method closeOnClickOutside (line 56) | closeOnClickOutside({ target }) {
method preventCloseOnMorphing (line 60) | preventCloseOnMorphing(event) {
method loadLazyFrames (line 67) | loadLazyFrames() {
method captureKey (line 71) | captureKey(event) {
method #setupFocus (line 75) | #setupFocus() {
FILE: app/javascript/controllers/dialog_manager_controller.js
method connect (line 4) | connect() {
method disconnect (line 8) | disconnect() {
method handleDialogShow (line 12) | handleDialogShow(event) {
method #dialogControllers (line 21) | get #dialogControllers() {
FILE: app/javascript/controllers/drag_and_drop_controller.js
method dragStart (line 11) | async dragStart(event) {
method dragOver (line 23) | dragOver(event) {
method drop (line 40) | async drop(event) {
method dragEnd (line 55) | dragEnd() {
method #itemContaining (line 69) | #itemContaining(element) {
method #containerContaining (line 73) | #containerContaining(element) {
method #clearContainerHoverClasses (line 77) | #clearContainerHoverClasses() {
method #applyContainerCssVariableToDraggedItem (line 81) | #applyContainerCssVariableToDraggedItem(container) {
method #restoreOriginalDraggedItemCssVariable (line 88) | #restoreOriginalDraggedItemCssVariable() {
method #containerCssVariableFor (line 95) | #containerCssVariableFor(container) {
method #increaseCounter (line 103) | #increaseCounter(container) {
method #decreaseCounter (line 107) | #decreaseCounter(container) {
method #modifyCounter (line 111) | #modifyCounter(container, fn) {
method #insertDraggedItem (line 122) | #insertDraggedItem(container, item) {
method #submitDropRequest (line 138) | async #submitDropRequest(item, container) {
method #reloadSourceFrame (line 146) | #reloadSourceFrame(sourceContainer) {
FILE: app/javascript/controllers/drag_and_strum_controller.js
constant INSTRUMENTS (line 3) | const INSTRUMENTS = [
method connect (line 49) | connect() {
method disconnect (line 55) | disconnect() {
method handleKeyDown (line 59) | handleKeyDown(event) {
method dragEnter (line 69) | dragEnter(event) {
method #getInstrumentIndex (line 80) | #getInstrumentIndex(event) {
method #preloadAudioFiles (line 85) | #preloadAudioFiles(instrumentIndex) {
method #containerContaining (line 98) | #containerContaining(element) {
method #playSound (line 102) | #playSound() {
FILE: app/javascript/controllers/element_removal_controller.js
method remove (line 4) | remove() {
FILE: app/javascript/controllers/expandable_on_native_controller.js
method shouldLoad (line 5) | static get shouldLoad() {
method connect (line 11) | connect() {
FILE: app/javascript/controllers/fetch_on_visible_controller.js
method connect (line 7) | connect() {
method #observe (line 11) | #observe() {
method #fetch (line 22) | #fetch() {
FILE: app/javascript/controllers/filter_controller.js
method initialize (line 8) | initialize() {
method filter (line 12) | filter() {
method clearInput (line 24) | clearInput() {
FILE: app/javascript/controllers/filter_form_controller.js
method clearCategory (line 4) | clearCategory({ params: { name } }) {
FILE: app/javascript/controllers/filter_settings_controller.js
method initialize (line 10) | initialize() {
method connect (line 14) | connect() {
method change (line 18) | change(event) {
method resetIfNoFiltering (line 23) | resetIfNoFiltering(event) {
method fieldTargetConnected (line 30) | async fieldTargetConnected(field) {
method submitToGenericCardsView (line 34) | submitToGenericCardsView() {
method #toggle (line 40) | #toggle() {
method #hasFiltersSet (line 44) | get #hasFiltersSet() {
method #isFieldSet (line 48) | #isFieldSet(field) {
method #defaultValueForField (line 57) | #defaultValueForField(field) {
method #refreshSaveToggleButton (line 62) | #refreshSaveToggleButton() {
method #collectFilterFormData (line 69) | #collectFilterFormData() {
method #showNoFilteringUrl (line 82) | #showNoFilteringUrl() {
FILE: app/javascript/controllers/form_controller.js
method initialize (line 13) | initialize() {
method compositionStart (line 18) | compositionStart() {
method compositionEnd (line 22) | compositionEnd() {
method submit (line 26) | submit() {
method preventEmptySubmit (line 30) | preventEmptySubmit(event) {
method preventComposingSubmit (line 46) | preventComposingSubmit(event) {
method debouncedSubmit (line 52) | debouncedSubmit(event) {
method submitToTopTarget (line 56) | submitToTopTarget(event) {
method reset (line 65) | reset() {
method cancel (line 69) | cancel() {
method preventAttachment (line 73) | preventAttachment(event) {
method disableSubmitWhenInvalid (line 77) | async disableSubmitWhenInvalid(event) {
method select (line 87) | select(event) {
method blurActiveInput (line 91) | blurActiveInput() {
FILE: app/javascript/controllers/frame_controller.js
method morphRender (line 5) | morphRender({ detail }) {
method morphReload (line 11) | morphReload(event) {
method reload (line 19) | reload() {
FILE: app/javascript/controllers/frame_reloader_controller.js
method connect (line 8) | connect() {
method reload (line 12) | reload() {
FILE: app/javascript/controllers/hotkey_controller.js
method click (line 4) | click(event) {
method focus (line 11) | focus(event) {
method #shouldIgnore (line 18) | #shouldIgnore(event) {
method #isClickable (line 22) | get #isClickable() {
FILE: app/javascript/controllers/knob_controller.js
method connect (line 6) | connect() {
method optionChanged (line 10) | optionChanged({ target }) {
method sliderChanged (line 14) | sliderChanged({ target }) {
method #index (line 18) | set #index(index) {
method #selectedOption (line 24) | get #selectedOption() {
method #optionForIndex (line 30) | #optionForIndex(index) {
FILE: app/javascript/controllers/lightbox_controller.js
method imageTargetConnected (line 6) | imageTargetConnected(element) {
method imageTargetDisconnected (line 10) | imageTargetDisconnected(element) {
method #open (line 19) | #open(link) {
method handleTransitionEnd (line 25) | handleTransitionEnd(event) {
method reset (line 31) | reset() {
method #set (line 37) | #set(target) {
FILE: app/javascript/controllers/local_save_controller.js
method initialize (line 8) | initialize() {
method connect (line 12) | connect() {
method submit (line 16) | submit({ detail: { success } }) {
method save (line 22) | save() {
method restoreContent (line 31) | async restoreContent() {
method #clear (line 44) | #clear() {
method #triggerChangeEvent (line 48) | #triggerChangeEvent(newValue) {
FILE: app/javascript/controllers/local_time_controller.js
constant DEFAULT_LOCALE (line 4) | const DEFAULT_LOCALE = "en-US"
method initialize (line 13) | initialize() {
method connect (line 27) | connect() {
method disconnect (line 31) | disconnect() {
method refreshAll (line 35) | refreshAll() {
method refreshTarget (line 43) | refreshTarget(event) {
method timeTargetConnected (line 49) | timeTargetConnected(target) {
method dateTargetConnected (line 53) | dateTargetConnected(target) {
method datetimeTargetConnected (line 57) | datetimeTargetConnected(target) {
method shortdateTargetConnected (line 61) | shortdateTargetConnected(target) {
method agoTargetConnected (line 65) | agoTargetConnected(target) {
method indaysTargetConnected (line 69) | indaysTargetConnected(target) {
method daysagoTargetConnected (line 73) | daysagoTargetConnected(target) {
method agoorweekdayTargetConnected (line 77) | agoorweekdayTargetConnected(target) {
method timeordateTargetConnected (line 81) | timeordateTargetConnected(target) {
method #refreshRelativeTimes (line 85) | #refreshRelativeTimes() {
method #formatTime (line 91) | #formatTime(formatter, target) {
class AgoFormatter (line 98) | class AgoFormatter {
method format (line 99) | format(dt) {
method #pluralize (line 119) | #pluralize(word, quantity) {
class DaysAgoFormatter (line 126) | class DaysAgoFormatter {
method format (line 127) | format(date) {
class DaysAgoOrWeekdayFormatter (line 136) | class DaysAgoOrWeekdayFormatter {
method format (line 137) | format(date) {
class InDaysFormatter (line 148) | class InDaysFormatter {
method format (line 149) | format(date) {
class TimeOrDateFormatter (line 158) | class TimeOrDateFormatter {
method format (line 159) | format(date) {
function styleableValue (line 170) | function styleableValue(value) {
FILE: app/javascript/controllers/magic_link_controller.js
method submitOnEnter (line 7) | submitOnEnter(event) {
method submitOnPaste (line 12) | submitOnPaste() {
method submit (line 16) | submit() {
FILE: app/javascript/controllers/multi_selection_combobox_controller.js
method connect (line 15) | connect() {
method change (line 19) | change(event) {
method refresh (line 26) | refresh() {
method clear (line 32) | clear(event) {
method #selectedLabel (line 39) | get #selectedLabel() {
method #toggleSelection (line 54) | #toggleSelection(item) {
method isAnExclusiveSelectionItemInvolved (line 74) | isAnExclusiveSelectionItemInvolved(item) {
method #isExclusiveSelection (line 78) | #isExclusiveSelection(item) {
method #updateHiddenFields (line 82) | #updateHiddenFields() {
method #deselectAll (line 88) | #deselectAll() {
method #selectedItems (line 94) | get #selectedItems() {
method #selectedValues (line 100) | #selectedValues() {
method #clearHiddenFields (line 104) | #clearHiddenFields() {
method #renameHiddenFields (line 110) | #renameHiddenFields(fieldName) {
method #hiddenFields (line 116) | get #hiddenFields() {
method #addHiddenFields (line 120) | #addHiddenFields() {
method #updateFilterShow (line 129) | #updateFilterShow() {
FILE: app/javascript/controllers/nav_section_expander_controller.js
method sectionTargetConnected (line 7) | sectionTargetConnected() {
method toggle (line 11) | toggle(event) {
method showWhileFiltering (line 23) | showWhileFiltering() {
method #expandAll (line 31) | #expandAll() {
method #restoreToggles (line 38) | #restoreToggles() {
method #localStorageKeyFor (line 46) | #localStorageKeyFor(section) {
FILE: app/javascript/controllers/navigable_list_controller.js
method shouldLoad (line 23) | static get shouldLoad() {
method connect (line 27) | connect() {
method reset (line 37) | reset(event) {
method navigate (line 45) | navigate(event) {
method select (line 50) | select({ target }) {
method hoverSelect (line 54) | hoverSelect({ currentTarget }) {
method selectCurrentOrReset (line 58) | selectCurrentOrReset(event) {
method selectFirst (line 66) | selectFirst() {
method selectLast (line 70) | selectLast() {
method deselectWhenClickingOutside (line 74) | deselectWhenClickingOutside(event) {
method selectItem (line 84) | async selectItem(item, skipFocus = false) {
method isSelected (line 100) | isSelected(item) {
method #setCurrentFrom (line 106) | async #setCurrentFrom(element) {
method #parentNavigableListController (line 114) | get #parentNavigableListController() {
method #selectCurrentElementInParent (line 122) | async #selectCurrentElementInParent() {
method #clearSelection (line 133) | #clearSelection() {
method #refreshActiveDescendant (line 139) | #refreshActiveDescendant() {
method #activateNestedNavigableList (line 146) | #activateNestedNavigableList() {
method #nestedNavigableListController (line 155) | #nestedNavigableListController() {
method #activateManualSelection (line 163) | #activateManualSelection() {
method #relayNavigationToParentNavigableList (line 171) | #relayNavigationToParentNavigableList(event) {
method #selectPrevious (line 179) | #selectPrevious() {
method #selectNext (line 186) | #selectNext() {
method #handleArrowKey (line 193) | #handleArrowKey(event, fn) {
method #clickCurrentItem (line 201) | #clickCurrentItem(event) {
method #isFocusContainedOnNavigableItem (line 209) | get #isFocusContainedOnNavigableItem() {
method #toggleCurrentItem (line 213) | #toggleCurrentItem(event) {
method #visibleItems (line 228) | get #visibleItems() {
method visibleItems (line 235) | get visibleItems() {
method clearSelection (line 239) | clearSelection() {
method hasFocus (line 244) | get hasFocus() {
method ArrowDown (line 249) | ArrowDown(event) {
method ArrowUp (line 255) | ArrowUp(event) {
method ArrowRight (line 261) | ArrowRight(event) {
method ArrowLeft (line 266) | ArrowLeft(event) {
method Enter (line 271) | Enter(event) {
FILE: app/javascript/controllers/notifications_controller.js
method connect (line 9) | async connect() {
method attemptToSubscribe (line 29) | async attemptToSubscribe() {
method isEnabled (line 41) | async isEnabled() {
method #allowed (line 50) | get #allowed() {
method #getServiceWorkerRegistration (line 54) | async #getServiceWorkerRegistration() {
method #registerServiceWorker (line 58) | async #registerServiceWorker() {
method #subscribe (line 63) | async #subscribe(registration) {
method #syncPushSubscription (line 71) | async #syncPushSubscription(subscription) {
method #showButtonToSubscribe (line 81) | #showButtonToSubscribe() {
method #requestPermissionAndSubscribe (line 86) | async #requestPermissionAndSubscribe(registration) {
method #vapidPublicKey (line 91) | get #vapidPublicKey() {
method #extractJsonPayloadAsString (line 96) | #extractJsonPayloadAsString(subscription) {
method #urlBase64ToUint8Array (line 102) | #urlBase64ToUint8Array(base64String) {
FILE: app/javascript/controllers/outlet_auto_save_controller.js
method change (line 6) | change(event) {
method submit (line 10) | submit() {
FILE: app/javascript/controllers/pagination_controller.js
constant DELAY_BEFORE_OBSERVING (line 7) | const DELAY_BEFORE_OBSERVING = 400
method initialize (line 17) | initialize() {
method disconnect (line 23) | disconnect() {
method activate (line 27) | async activate() {
method paginationLinkTargetConnected (line 35) | async paginationLinkTargetConnected(linkElement) {
method loadPage (line 44) | loadPage({ target }) {
method #loadPaginationLink (line 56) | #loadPaginationLink(linkElement) {
method #closestSiblingTo (line 62) | #closestSiblingTo(element) {
method #expandPaginationLink (line 66) | async #expandPaginationLink(linkElement) {
method #replacePaginationLinkWithFrameContents (line 78) | async #replacePaginationLinkWithFrameContents(linkElement) {
method #loadHtmlFrom (line 82) | async #loadHtmlFrom(linkElement) {
method #replacePaginationLinkWithFrame (line 90) | #replacePaginationLinkWithFrame(linkElement) {
method #buildTurboFrameFor (line 95) | #buildTurboFrameFor(linkElement) {
method #keepScrollPositionOnFrameRender (line 108) | async #keepScrollPositionOnFrameRender(turboFrame, linkElement) {
method #insertTurboFrameAtPosition (line 114) | #insertTurboFrameAtPosition(linkElement, turboFrame) {
FILE: app/javascript/controllers/reaction_delete_controller.js
method connect (line 8) | connect() {
method reveal (line 14) | reveal() {
method perform (line 22) | perform() {
method #setAccessibleAttributes (line 26) | #setAccessibleAttributes() {
method #currentUserIsReacter (line 33) | get #currentUserIsReacter() {
FILE: app/javascript/controllers/reaction_emoji_controller.js
method insertEmoji (line 6) | insertEmoji(event) {
FILE: app/javascript/controllers/related_element_controller.js
method connect (line 7) | connect() {
method highlight (line 11) | highlight(event) {
method unhighlight (line 15) | unhighlight() {
method #highlight (line 19) | #highlight(groupValue) {
FILE: app/javascript/controllers/retarget_links_controller.js
method connect (line 4) | connect() {
method #retargetLink (line 8) | #retargetLink(link) {
method #targetsSameDomain (line 12) | #targetsSameDomain(link) {
FILE: app/javascript/controllers/scroll_to_controller.js
method connect (line 6) | connect() {
method #scrollTargetIntoView (line 10) | #scrollTargetIntoView() {
FILE: app/javascript/controllers/search_form_controller.js
method clearInput (line 6) | clearInput() {
FILE: app/javascript/controllers/soft_keyboard_controller.js
method shouldLoad (line 7) | static get shouldLoad() {
method open (line 13) | async open(event) {
method #focusOnFakeInput (line 18) | #focusOnFakeInput() {
method #removeOnFocusOut (line 30) | async #removeOnFocusOut(element) {
FILE: app/javascript/controllers/syntax_highlight_controller.js
method connect (line 5) | connect() {
FILE: app/javascript/controllers/theme_controller.js
method connect (line 6) | connect() {
method setLight (line 10) | setLight() {
method setDark (line 14) | setDark() {
method setAuto (line 18) | setAuto() {
method #storedTheme (line 22) | get #storedTheme() {
method #theme (line 26) | set #theme(theme) {
method #applyStoredTheme (line 52) | #applyStoredTheme() {
method #updateButtons (line 56) | #updateButtons() {
FILE: app/javascript/controllers/timezone_cookie_controller.js
method connect (line 4) | connect() {
method #setTimezoneCookie (line 8) | #setTimezoneCookie() {
FILE: app/javascript/controllers/toggle_class_controller.js
method toggle (line 7) | toggle() {
method add (line 11) | add() {
method remove (line 15) | remove() {
method checkAll (line 19) | checkAll() {
method checkNone (line 25) | checkNone() {
FILE: app/javascript/controllers/toggle_enable_controller.js
method toggle (line 6) | toggle() {
FILE: app/javascript/controllers/tooltip_controller.js
method connect (line 7) | connect() {
method disconnect (line 12) | disconnect() {
method mouseEnter (line 17) | mouseEnter(event) {
method mouseOut (line 21) | mouseOut(event) {
method #tooltipElement (line 25) | get #tooltipElement() {
method #tooltipText (line 29) | get #tooltipText() {
FILE: app/javascript/controllers/touch_placeholder_controller.js
method shouldLoad (line 5) | static get shouldLoad() {
method connect (line 11) | connect() {
FILE: app/javascript/controllers/turbo_navigation_controller.js
method rememberLocation (line 7) | rememberLocation() {
method backIfSamePath (line 12) | backIfSamePath(event) {
method referrerBackLinkTargetConnected (line 24) | referrerBackLinkTargetConnected(link) {
method #referrerPath (line 37) | get #referrerPath() {
method #referrerUrl (line 42) | get #referrerUrl() {
method #referrerLabel (line 46) | get #referrerLabel() {
FILE: app/javascript/controllers/upload_preview_controller.js
method previewImage (line 6) | previewImage() {
method previewFileName (line 13) | previewFileName() {
method #showFileName (line 17) | #showFileName() {
method #showPlaceholder (line 23) | #showPlaceholder() {
method #file (line 28) | get #file() {
FILE: app/javascript/helpers/bridge/viewport_helpers.js
method top (line 5) | get top() {
method height (line 8) | get height() {
function update (line 13) | function update() {
FILE: app/javascript/helpers/date_helpers.js
function differenceInDays (line 1) | function differenceInDays(fromDate, toDate) {
function signedDifferenceInDays (line 5) | function signedDifferenceInDays(fromDate, toDate) {
function beginningOfDay (line 9) | function beginningOfDay(date) {
function secondsToDate (line 13) | function secondsToDate(seconds) {
FILE: app/javascript/helpers/form_helpers.js
function submitForm (line 3) | async function submitForm(form) {
FILE: app/javascript/helpers/html_helpers.js
function createElement (line 1) | function createElement(name, properties) {
FILE: app/javascript/helpers/orientation_helpers.js
constant EDGE_THRESHOLD (line 1) | const EDGE_THRESHOLD = 16
function orient (line 3) | function orient({ target, anchor = null, reset = false }) {
function orientLeft (line 21) | function orientLeft({ el, targetGeometry, anchorGeometry }) {
function orientRight (line 27) | function orientRight({ el, targetGeometry, anchorGeometry }) {
function geometry (line 33) | function geometry(el) {
FILE: app/javascript/helpers/platform_helpers.js
function isTouchDevice (line 1) | function isTouchDevice() {
function isIos (line 5) | function isIos() {
function isAndroid (line 9) | function isAndroid() {
function isMobile (line 13) | function isMobile() {
function isNative (line 17) | function isNative() {
FILE: app/javascript/helpers/scroll_helpers.js
function keepingScrollPosition (line 1) | async function keepingScrollPosition(element, promise) {
function isFullyVisible (line 15) | function isFullyVisible(element, container = document.documentElement) {
function isScrolledToBottom (line 25) | function isScrolledToBottom(element, threshold = 100) {
function scrollToBottom (line 29) | function scrollToBottom(element) {
function scrollIntoView (line 33) | function scrollIntoView(element, options = { inline: "center", block: "c...
function findNearestScrollableYAncestor (line 39) | function findNearestScrollableYAncestor(refElement) {
function findNearestScrollableXAncestor (line 50) | function findNearestScrollableXAncestor(refElement) {
function findNearest (line 61) | function findNearest(element, fn) {
FILE: app/javascript/helpers/text_helpers.js
function isMultiLineString (line 1) | function isMultiLineString(string) {
function normalizeFilteredText (line 5) | function normalizeFilteredText(string) {
function filterMatches (line 11) | function filterMatches(text, potentialMatch) {
function toSentence (line 15) | function toSentence(array, options = {}) {
FILE: app/javascript/helpers/timing_helpers.js
function throttle (line 1) | function throttle(fn, delay = 1000) {
function debounce (line 12) | function debounce(fn, delay = 1000) {
function nextEventLoopTick (line 21) | function nextEventLoopTick() {
function onNextEventLoopTick (line 25) | function onNextEventLoopTick(callback) {
function nextEvent (line 29) | function nextEvent(element, eventName) {
function nextFrame (line 33) | function nextFrame() {
function nextEventNamed (line 37) | function nextEventNamed(eventName, element = window) {
function delay (line 41) | function delay(ms) {
FILE: app/javascript/initializers/current.js
class Current (line 1) | class Current {
method user (line 2) | get user() {
method #extractContentFromMetaTag (line 10) | #extractContentFromMetaTag(name) {
FILE: app/javascript/lib/action_pack/passkey.js
function setup (line 46) | function setup() {
function teardown (line 66) | function teardown() {
function createPasskey (line 81) | async function createPasskey(button) {
function passkeysAvailable (line 109) | function passkeysAvailable() {
function getCreationOptions (line 115) | function getCreationOptions() {
function getOptions (line 120) | function getOptions(name) {
function refreshChallenge (line 130) | async function refreshChallenge(options) {
function fillCreateForm (line 152) | function fillCreateForm(form, passkey) {
function signInWithPasskey (line 167) | async function signInWithPasskey(button) {
function getRequestOptions (line 197) | function getRequestOptions() {
function fillSignInForm (line 202) | function fillSignInForm(form, passkey) {
function attemptConditionalMediation (line 212) | async function attemptConditionalMediation() {
function conditionalMediationAvailable (line 237) | async function conditionalMediationAvailable() {
function isConditionalMediationFormPresent (line 244) | function isConditionalMediationFormPresent() {
FILE: app/javascript/lib/action_pack/webauthn.js
function register (line 9) | async function register(options) {
function authenticate (line 24) | async function authenticate(options, { signal, mediation } = {}) {
function prepareCreationOptions (line 38) | function prepareCreationOptions(options) {
function prepareRequestOptions (line 54) | function prepareRequestOptions(options) {
function base64urlToBuffer (line 72) | function base64urlToBuffer(base64url) {
function bufferToBase64url (line 79) | function bufferToBase64url(buffer) {
FILE: app/jobs/account/data_import_job.rb
class Account::DataImportJob (line 1) | class Account::DataImportJob < ApplicationJob
method perform (line 7) | def perform(import)
FILE: app/jobs/account/incinerate_due_job.rb
class Account::IncinerateDueJob (line 1) | class Account::IncinerateDueJob < ApplicationJob
method perform (line 6) | def perform
FILE: app/jobs/application_job.rb
class ApplicationJob (line 1) | class ApplicationJob < ActiveJob::Base
FILE: app/jobs/board/clean_inaccessible_data_job.rb
class Board::CleanInaccessibleDataJob (line 1) | class Board::CleanInaccessibleDataJob < ApplicationJob
method perform (line 4) | def perform(user, board)
FILE: app/jobs/card/activity_spike/detection_job.rb
class Card::ActivitySpike::DetectionJob (line 1) | class Card::ActivitySpike::DetectionJob < ApplicationJob
method perform (line 4) | def perform(card)
FILE: app/jobs/card/clean_inaccessible_data_job.rb
class Card::CleanInaccessibleDataJob (line 1) | class Card::CleanInaccessibleDataJob < ApplicationJob
method perform (line 4) | def perform(card)
FILE: app/jobs/card/remove_inaccessible_notifications_job.rb
class Card::RemoveInaccessibleNotificationsJob (line 1) | class Card::RemoveInaccessibleNotificationsJob < ApplicationJob
method perform (line 4) | def perform(card)
FILE: app/jobs/concerns/smtp_delivery_error_handling.rb
type SmtpDeliveryErrorHandling (line 1) | module SmtpDeliveryErrorHandling
FILE: app/jobs/data_export_job.rb
class DataExportJob (line 1) | class DataExportJob < ApplicationJob
method perform (line 6) | def perform(export)
FILE: app/jobs/delete_unused_tags_job.rb
class DeleteUnusedTagsJob (line 1) | class DeleteUnusedTagsJob < ApplicationJob
method perform (line 2) | def perform
FILE: app/jobs/event/webhook_dispatch_job.rb
class Event::WebhookDispatchJob (line 3) | class Event::WebhookDispatchJob < ApplicationJob
method perform (line 10) | def perform(event)
FILE: app/jobs/mention/create_job.rb
class Mention::CreateJob (line 1) | class Mention::CreateJob < ApplicationJob
method perform (line 4) | def perform(record, mentioner:)
FILE: app/jobs/notification/bundle/deliver_all_job.rb
class Notification::Bundle::DeliverAllJob (line 1) | class Notification::Bundle::DeliverAllJob < ApplicationJob
method perform (line 6) | def perform
FILE: app/jobs/notification/bundle/deliver_job.rb
class Notification::Bundle::DeliverJob (line 1) | class Notification::Bundle::DeliverJob < ApplicationJob
method perform (line 8) | def perform(bundle)
FILE: app/jobs/notification/push_job.rb
class Notification::PushJob (line 1) | class Notification::PushJob < ApplicationJob
method perform (line 2) | def perform(notification)
FILE: app/jobs/notify_recipients_job.rb
class NotifyRecipientsJob (line 1) | class NotifyRecipientsJob < ApplicationJob
method perform (line 4) | def perform(notifiable)
FILE: app/jobs/push_notification_job.rb
class PushNotificationJob (line 1) | class PushNotificationJob < ApplicationJob
method perform (line 4) | def perform(notification)
FILE: app/jobs/storage/materialize_job.rb
class Storage::MaterializeJob (line 1) | class Storage::MaterializeJob < ApplicationJob
method perform (line 7) | def perform(owner)
FILE: app/jobs/storage/reconcile_job.rb
class Storage::ReconcileJob (line 1) | class Storage::ReconcileJob < ApplicationJob
class ReconcileAborted (line 2) | class ReconcileAborted < StandardError; end
method perform (line 11) | def perform(owner)
FILE: app/jobs/webhook/delivery_job.rb
class Webhook::DeliveryJob (line 1) | class Webhook::DeliveryJob < ApplicationJob
method perform (line 6) | def perform(delivery)
FILE: app/mailers/account_mailer.rb
class AccountMailer (line 1) | class AccountMailer < ApplicationMailer
method cancellation (line 2) | def cancellation(cancellation)
FILE: app/mailers/application_mailer.rb
class ApplicationMailer (line 1) | class ApplicationMailer < ActionMailer::Base
method default_url_options (line 9) | def default_url_options
FILE: app/mailers/concerns/mailers/unsubscribable.rb
type Mailers::Unsubscribable (line 1) | module Mailers::Unsubscribable
function set_unsubscribe_headers (line 8) | def set_unsubscribe_headers
FILE: app/mailers/export_mailer.rb
class ExportMailer (line 1) | class ExportMailer < ApplicationMailer
method completed (line 4) | def completed(export)
method export_download_url (line 12) | def export_download_url(export)
FILE: app/mailers/import_mailer.rb
class ImportMailer (line 1) | class ImportMailer < ApplicationMailer
method completed (line 2) | def completed(identity, account)
method failed (line 7) | def failed(import)
FILE: app/mailers/magic_link_mailer.rb
class MagicLinkMailer (line 1) | class MagicLinkMailer < ApplicationMailer
method sign_in_instructions (line 2) | def sign_in_instructions(magic_link)
FILE: app/mailers/notification/bundle_mailer.rb
class Notification::BundleMailer (line 1) | class Notification::BundleMailer < ApplicationMailer
method notification (line 6) | def notification(bundle)
FILE: app/mailers/user_mailer.rb
class UserMailer (line 1) | class UserMailer < ApplicationMailer
method email_change_confirmation (line 2) | def email_change_confirmation(email_address:, token:, user:)
FILE: app/models/access.rb
class Access (line 1) | class Access < ApplicationRecord
method accessed (line 12) | def accessed
method recently_accessed? (line 17) | def recently_accessed?
method clean_inaccessible_data_later (line 21) | def clean_inaccessible_data_later
FILE: app/models/account.rb
class Account (line 1) | class Account < ApplicationRecord
method create_with_owner (line 24) | def create_with_owner(account:, owner:)
method slug (line 32) | def slug
method account (line 36) | def account
method system_user (line 40) | def system_user
method active? (line 44) | def active?
method importing? (line 48) | def importing?
method assign_external_account_id (line 53) | def assign_external_account_id
FILE: app/models/account/cancellable.rb
type Account::Cancellable (line 1) | module Account::Cancellable
function cancel (line 11) | def cancel(initiated_by: Current.user)
function reactivate (line 23) | def reactivate
function cancelled? (line 33) | def cancelled?
function cancellable? (line 37) | def cancellable?
FILE: app/models/account/cancellation.rb
class Account::Cancellation (line 1) | class Account::Cancellation < ApplicationRecord
FILE: app/models/account/data_transfer/account_record_set.rb
class Account::DataTransfer::AccountRecordSet (line 1) | class Account::DataTransfer::AccountRecordSet < Account::DataTransfer::R...
method initialize (line 13) | def initialize(account)
method records (line 18) | def records
method export_record (line 22) | def export_record(account)
method files (line 26) | def files
method import_batch (line 30) | def import_batch(files)
method check_record (line 39) | def check_record(file_path)
FILE: app/models/account/data_transfer/action_text/rich_text_record_set.rb
class Account::DataTransfer::ActionText::RichTextRecordSet (line 1) | class Account::DataTransfer::ActionText::RichTextRecordSet < Account::Da...
method initialize (line 13) | def initialize(account)
method records (line 18) | def records
method export_record (line 22) | def export_record(rich_text)
method files (line 27) | def files
method import_batch (line 31) | def import_batch(files)
method check_record (line 41) | def check_record(file_path)
method transform_body_for_export (line 57) | def transform_body_for_export(content)
method convert_sgids_to_gids (line 64) | def convert_sgids_to_gids(content)
method relativize_urls (line 83) | def relativize_urls(html)
method transform_body_for_import (line 102) | def transform_body_for_import(body)
method convert_gids_to_sgids (line 111) | def convert_gids_to_sgids(fragment)
method replace_account_slugs (line 132) | def replace_account_slugs(fragment)
FILE: app/models/account/data_transfer/active_storage/attachment_record_set.rb
class Account::DataTransfer::ActiveStorage::AttachmentRecordSet (line 1) | class Account::DataTransfer::ActiveStorage::AttachmentRecordSet < Accoun...
method initialize (line 2) | def initialize(account)
method records (line 7) | def records
FILE: app/models/account/data_transfer/active_storage/blob_record_set.rb
class Account::DataTransfer::ActiveStorage::BlobRecordSet (line 1) | class Account::DataTransfer::ActiveStorage::BlobRecordSet < Account::Dat...
method initialize (line 2) | def initialize(account)
method records (line 11) | def records
method excluded_blob_ids (line 15) | def excluded_blob_ids
method import_batch (line 19) | def import_batch(files)
FILE: app/models/account/data_transfer/active_storage/file_record_set.rb
class Account::DataTransfer::ActiveStorage::FileRecordSet (line 1) | class Account::DataTransfer::ActiveStorage::FileRecordSet < Account::Dat...
method initialize (line 2) | def initialize(account)
method records (line 7) | def records
method excluded_blob_ids (line 11) | def excluded_blob_ids
method export_record (line 15) | def export_record(blob)
method files (line 23) | def files
method import_batch (line 27) | def import_batch(files)
method old_key_to_blob_id (line 42) | def old_key_to_blob_id
method build_old_key_to_blob_id (line 46) | def build_old_key_to_blob_id
method with_zip (line 57) | def with_zip(zip)
method check_record (line 62) | def check_record(file_path)
FILE: app/models/account/data_transfer/entropy_record_set.rb
class Account::DataTransfer::EntropyRecordSet (line 1) | class Account::DataTransfer::EntropyRecordSet < Account::DataTransfer::R...
method initialize (line 2) | def initialize(account)
method import_batch (line 7) | def import_batch(files)
FILE: app/models/account/data_transfer/manifest.rb
class Account::DataTransfer::Manifest (line 1) | class Account::DataTransfer::Manifest
method initialize (line 4) | def initialize(account)
method each_record_set (line 8) | def each_record_set(start: nil)
method record_sets (line 25) | def record_sets
method build_record_sets (line 67) | def build_record_sets(*models)
method set_importable_model_names (line 73) | def set_importable_model_names(record_sets)
FILE: app/models/account/data_transfer/record_set.rb
class Account::DataTransfer::RecordSet (line 1) | class Account::DataTransfer::RecordSet
class IntegrityError (line 2) | class IntegrityError < StandardError; end
class ConflictError (line 3) | class ConflictError < IntegrityError; end
method initialize (line 11) | def initialize(account:, model:, attributes: nil, importable_model_nam...
method export (line 18) | def export(to:, start: nil)
method import (line 28) | def import(from:, start: nil, callback: nil)
method check (line 40) | def check(from:, start: nil, callback: nil)
method with_zip (line 55) | def with_zip(zip)
method records (line 63) | def records
method export_record (line 67) | def export_record(record)
method files (line 71) | def files
method import_batch (line 75) | def import_batch(files)
method check_record (line 86) | def check_record(file_path)
method check_associations_dont_exist (line 106) | def check_associations_dont_exist(data)
method check_association_doesnt_exist (line 116) | def check_association_doesnt_exist(data, association, associated_id)
method verify_model_type (line 129) | def verify_model_type(type_name)
method skip_to (line 137) | def skip_to(file_list, last_id)
method load (line 147) | def load(file_path)
method model_dir (line 153) | def model_dir
FILE: app/models/account/data_transfer/user_record_set.rb
class Account::DataTransfer::UserRecordSet (line 1) | class Account::DataTransfer::UserRecordSet < Account::DataTransfer::Reco...
method initialize (line 13) | def initialize(account)
method records (line 18) | def records
method export_record (line 22) | def export_record(user)
method files (line 26) | def files
method import_batch (line 30) | def import_batch(files)
method check_record (line 49) | def check_record(file_path)
FILE: app/models/account/entropic.rb
type Account::Entropic (line 1) | module Account::Entropic
FILE: app/models/account/export.rb
class Account::Export (line 1) | class Account::Export < Export
method filename (line 3) | def filename
method populate_zip (line 7) | def populate_zip(zip)
FILE: app/models/account/external_id_sequence.rb
class Account::ExternalIdSequence (line 2) | class Account::ExternalIdSequence < ApplicationRecord
method next (line 4) | def next
method value (line 10) | def value
method with_lock (line 15) | def with_lock
method initial_value (line 22) | def initial_value
FILE: app/models/account/import.rb
class Account::Import (line 1) | class Account::Import < ApplicationRecord
method cleanup (line 14) | def self.cleanup
method process_later (line 18) | def process_later
method check (line 22) | def check(start: nil, callback: nil)
method process (line 41) | def process(start: nil, callback: nil)
method cleanup (line 66) | def cleanup
method mark_completed (line 72) | def mark_completed
method mark_as_failed (line 77) | def mark_as_failed(failure_reason = nil)
method reconcile_cards_count (line 82) | def reconcile_cards_count
method add_importer_to_all_access_boards (line 86) | def add_importer_to_all_access_boards
method reconcile_account_storage (line 94) | def reconcile_account_storage
FILE: app/models/account/incineratable.rb
type Account::Incineratable (line 1) | module Account::Incineratable
function incinerate (line 12) | def incinerate
FILE: app/models/account/join_code.rb
class Account::JoinCode (line 1) | class Account::JoinCode < ApplicationRecord
method redeem_if (line 13) | def redeem_if(&block)
method active? (line 19) | def active?
method reset (line 23) | def reset
method generate_code (line 30) | def generate_code
FILE: app/models/account/multi_tenantable.rb
type Account::MultiTenantable (line 1) | module Account::MultiTenantable
function accepting_signups? (line 9) | def accepting_signups?
FILE: app/models/account/seedeable.rb
type Account::Seedeable (line 1) | module Account::Seedeable
function setup_customer_template (line 4) | def setup_customer_template
FILE: app/models/account/seeder.rb
class Account::Seeder (line 1) | class Account::Seeder
method initialize (line 4) | def initialize(account, creator)
method seed (line 9) | def seed
method seed! (line 15) | def seed!
method populate (line 23) | def populate
method delete_everything (line 99) | def delete_everything
FILE: app/models/account/storage.rb
type Account::Storage (line 1) | module Account::Storage
function calculate_real_storage_bytes (line 6) | def calculate_real_storage_bytes
FILE: app/models/admin.rb
type Admin (line 1) | module Admin
FILE: app/models/application_platform.rb
class ApplicationPlatform (line 1) | class ApplicationPlatform < PlatformAgent
method ios? (line 2) | def ios?
method android? (line 6) | def android?
method mac? (line 10) | def mac?
method chrome? (line 14) | def chrome?
method edge? (line 18) | def edge?
method firefox? (line 22) | def firefox?
method safari? (line 26) | def safari?
method mobile? (line 30) | def mobile?
method desktop? (line 34) | def desktop?
method native? (line 38) | def native?
method windows? (line 42) | def windows?
method bridge_name (line 46) | def bridge_name
method bridge_components (line 53) | def bridge_components
method type (line 57) | def type
method operating_system (line 69) | def operating_system
method extract_list_from_native_user_agent (line 83) | def extract_list_from_native_user_agent(prefix)
FILE: app/models/application_record.rb
class ApplicationRecord (line 1) | class ApplicationRecord < ActiveRecord::Base
FILE: app/models/assignment.rb
class Assignment (line 1) | class Assignment < ApplicationRecord
method within_limit (line 13) | def within_limit
FILE: app/models/board.rb
class Board (line 1) | class Board < ApplicationRecord
FILE: app/models/board/accessible.rb
type Board::Accessible (line 1) | module Board::Accessible
function revise (line 6) | def revise(granted: [], revoked: [])
function grant_to (line 13) | def grant_to(users)
function revoke_from (line 17) | def revoke_from(users)
function accessed_by (line 31) | def accessed_by(user)
function access_for (line 35) | def access_for(user)
function accessible_to? (line 39) | def accessible_to?(user)
function clean_inaccessible_data_for (line 43) | def clean_inaccessible_data_for(user)
function watchers (line 52) | def watchers
function grant_access_to_creator (line 57) | def grant_access_to_creator
function grant_access_to_everyone (line 61) | def grant_access_to_everyone
function mentions_for_user (line 65) | def mentions_for_user(user)
function notifications_for_user (line 79) | def notifications_for_user(user)
function watches_for (line 100) | def watches_for(user)
function pins_for (line 104) | def pins_for(user)
FILE: app/models/board/auto_postponing.rb
type Board::AutoPostponing (line 1) | module Board::AutoPostponing
function set_default_auto_postpone_period (line 9) | def set_default_auto_postpone_period
FILE: app/models/board/broadcastable.rb
type Board::Broadcastable (line 1) | module Board::Broadcastable
FILE: app/models/board/cards.rb
type Board::Cards (line 1) | module Board::Cards
FILE: app/models/board/entropic.rb
type Board::Entropic (line 1) | module Board::Entropic
function entropy (line 9) | def entropy
function auto_postpone_period= (line 13) | def auto_postpone_period=(new_value)
function auto_postpone_period_in_days= (line 18) | def auto_postpone_period_in_days=(value)
FILE: app/models/board/publication.rb
class Board::Publication (line 1) | class Board::Publication < ApplicationRecord
FILE: app/models/board/publishable.rb
type Board::Publishable (line 1) | module Board::Publishable
function find_by_published_key (line 10) | def find_by_published_key(key)
function published? (line 15) | def published?
function publish (line 20) | def publish
function unpublish (line 24) | def unpublish
FILE: app/models/board/storage.rb
type Board::Storage (line 1) | module Board::Storage
function board_for_storage_tracking (line 6) | def board_for_storage_tracking
function calculate_real_storage_bytes (line 18) | def calculate_real_storage_bytes
function card_ids (line 23) | def card_ids
function card_image_bytes (line 27) | def card_image_bytes
function card_embed_bytes (line 33) | def card_embed_bytes
function comment_embed_bytes (line 37) | def comment_embed_bytes
function board_embed_bytes (line 43) | def board_embed_bytes
function sum_embed_bytes_for (line 47) | def sum_embed_bytes_for(record_type, record_ids)
function sum_blob_bytes_in_batches (line 56) | def sum_blob_bytes_in_batches(base_scope, record_ids)
FILE: app/models/board/triageable.rb
type Board::Triageable (line 1) | module Board::Triageable
FILE: app/models/card.rb
class Card (line 1) | class Card < ApplicationRecord
method card (line 49) | def card
method to_param (line 53) | def to_param
method move_to (line 57) | def move_to(new_board)
method filled? (line 65) | def filled?
method set_default_title (line 70) | def set_default_title
method handle_board_change (line 74) | def handle_board_change
method track_board_change_event (line 87) | def track_board_change_event(old_board_name)
method assign_number (line 91) | def assign_number
FILE: app/models/card/accessible.rb
type Card::Accessible (line 1) | module Card::Accessible
function publicly_accessible? (line 8) | def publicly_accessible?
function clean_inaccessible_data (line 12) | def clean_inaccessible_data
function grant_access_to_assignees (line 19) | def grant_access_to_assignees
function clean_inaccessible_data_later (line 23) | def clean_inaccessible_data_later
FILE: app/models/card/activity_spike.rb
class Card::ActivitySpike (line 1) | class Card::ActivitySpike < ApplicationRecord
FILE: app/models/card/activity_spike/detector.rb
class Card::ActivitySpike::Detector (line 1) | class Card::ActivitySpike::Detector
method initialize (line 4) | def initialize(card)
method detect (line 8) | def detect
method has_activity_spike? (line 18) | def has_activity_spike?
method register_activity_spike (line 22) | def register_activity_spike
method multiple_people_commented? (line 28) | def multiple_people_commented?(minimum_comments: 3, minimum_participan...
method recent_period (line 37) | def recent_period
method card_was_just_assigned? (line 41) | def card_was_just_assigned?
method card_was_just_reopened? (line 45) | def card_was_just_reopened?
method card_was_just? (line 49) | def card_was_just?(action)
method last_event (line 53) | def last_event
FILE: app/models/card/assignable.rb
type Card::Assignable (line 1) | module Card::Assignable
function toggle_assignment (line 13) | def toggle_assignment(user)
function assigned_to? (line 17) | def assigned_to?(user)
function assigned? (line 21) | def assigned?
function assign (line 26) | def assign(user)
function unassign (line 37) | def unassign(user)
FILE: app/models/card/broadcastable.rb
type Card::Broadcastable (line 1) | module Card::Broadcastable
function remember_if_preview_changed (line 11) | def remember_if_preview_changed
function preview_changed? (line 15) | def preview_changed?
FILE: app/models/card/closeable.rb
type Card::Closeable (line 1) | module Card::Closeable
function closed? (line 15) | def closed?
function open? (line 19) | def open?
function closed_by (line 23) | def closed_by
function closed_at (line 27) | def closed_at
function close (line 31) | def close(user: Current.user)
function reopen (line 41) | def reopen(user: Current.user)
FILE: app/models/card/colored.rb
type Card::Colored (line 1) | module Card::Colored
function color (line 4) | def color
FILE: app/models/card/commentable.rb
type Card::Commentable (line 1) | module Card::Commentable
function commentable? (line 8) | def commentable?
function storage_transfer_records (line 18) | def storage_transfer_records
function storage_comment_ids_with_attachments (line 28) | def storage_comment_ids_with_attachments
FILE: app/models/card/entropic.rb
type Card::Entropic (line 1) | module Card::Entropic
function auto_postpone_all_due (line 27) | def auto_postpone_all_due
function entropy (line 34) | def entropy
function entropic? (line 38) | def entropic?
FILE: app/models/card/entropy.rb
class Card::Entropy (line 1) | class Card::Entropy
method for (line 5) | def for(card)
method initialize (line 12) | def initialize(card, auto_clean_period)
method auto_clean_at (line 17) | def auto_clean_at
method days_before_reminder (line 21) | def days_before_reminder
FILE: app/models/card/eventable.rb
type Card::Eventable (line 1) | module Card::Eventable
function event_was_created (line 12) | def event_was_created(event)
function touch_last_active_at (line 19) | def touch_last_active_at
function should_track_event? (line 25) | def should_track_event?
function track_title_change (line 29) | def track_title_change
function create_system_comment_for (line 35) | def create_system_comment_for(event)
FILE: app/models/card/eventable/system_commenter.rb
class Card::Eventable::SystemCommenter (line 1) | class Card::Eventable::SystemCommenter
method initialize (line 6) | def initialize(card, event)
method comment (line 10) | def comment
method comment_body (line 17) | def comment_body
method creator_name (line 42) | def creator_name
method assignee_names (line 46) | def assignee_names
method old_title (line 50) | def old_title
method new_title (line 54) | def new_title
method old_board (line 58) | def old_board
method new_board (line 62) | def new_board
method column (line 66) | def column
FILE: app/models/card/exportable.rb
type Card::Exportable (line 1) | module Card::Exportable
function export_json (line 5) | def export_json
function export_attachments (line 26) | def export_attachments
function export_html (line 33) | def export_html(rich_text)
function attachment_representation (line 41) | def attachment_representation(attachment)
function export_user (line 57) | def export_user(user)
function export_attachment_path (line 65) | def export_attachment_path(blob)
function collect_attachments (line 69) | def collect_attachments
function export_status (line 73) | def export_status
FILE: app/models/card/golden.rb
type Card::Golden (line 1) | module Card::Golden
function golden? (line 11) | def golden?
function gild (line 15) | def gild
function ungild (line 19) | def ungild
FILE: app/models/card/goldness.rb
class Card::Goldness (line 1) | class Card::Goldness < ApplicationRecord
FILE: app/models/card/mentions.rb
type Card::Mentions (line 1) | module Card::Mentions
function mentionable? (line 7) | def mentionable?
function should_check_mentions? (line 11) | def should_check_mentions?
FILE: app/models/card/multistep.rb
type Card::Multistep (line 1) | module Card::Multistep
FILE: app/models/card/not_now.rb
class Card::NotNow (line 1) | class Card::NotNow < ApplicationRecord
FILE: app/models/card/pinnable.rb
type Card::Pinnable (line 1) | module Card::Pinnable
function pinned_by? (line 10) | def pinned_by?(user)
function pin_for (line 14) | def pin_for(user)
function pin_by (line 18) | def pin_by(user)
function unpin_by (line 22) | def unpin_by(user)
function broadcast_pin_updates (line 27) | def broadcast_pin_updates
FILE: app/models/card/postponable.rb
type Card::Postponable (line 1) | module Card::Postponable
function postponed? (line 11) | def postponed?
function postponed_at (line 15) | def postponed_at
function postponed_by (line 19) | def postponed_by
function active? (line 23) | def active?
function auto_postpone (line 27) | def auto_postpone(**args)
function postpone (line 31) | def postpone(user: Current.user, event_name: :postponed)
function resume (line 41) | def resume
FILE: app/models/card/promptable.rb
type Card::Promptable (line 1) | module Card::Promptable
function to_prompt (line 8) | def to_prompt
function column_prompt_label (line 34) | def column_prompt_label
FILE: app/models/card/readable.rb
type Card::Readable (line 1) | module Card::Readable
function read_by (line 4) | def read_by(user)
function unread_by (line 8) | def unread_by(user)
function remove_inaccessible_notifications (line 12) | def remove_inaccessible_notifications
function remove_inaccessible_notifications_later (line 20) | def remove_inaccessible_notifications_later
function event_notification_sources (line 24) | def event_notification_sources
function comment_creation_events (line 28) | def comment_creation_events
function inaccessible_notifications_from (line 32) | def inaccessible_notifications_from(sources, accessible_user_ids)
function notification_sources (line 36) | def notification_sources
function mention_notification_sources (line 40) | def mention_notification_sources
function comment_mentions (line 44) | def comment_mentions
FILE: app/models/card/searchable.rb
type Card::Searchable (line 1) | module Card::Searchable
function search_title (line 13) | def search_title
function search_content (line 17) | def search_content
function search_card_id (line 21) | def search_card_id
function search_board_id (line 25) | def search_board_id
function searchable? (line 29) | def searchable?
FILE: app/models/card/stallable.rb
type Card::Stallable (line 1) | module Card::Stallable
function stalled? (line 17) | def stalled?
function last_activity_spike_at (line 23) | def last_activity_spike_at
function detect_activity_spikes (line 27) | def detect_activity_spikes
function remember_to_detect_activity_spikes (line 32) | def remember_to_detect_activity_spikes
function should_detect_activity_spikes? (line 36) | def should_detect_activity_spikes?
function detect_activity_spikes_later (line 40) | def detect_activity_spikes_later
FILE: app/models/card/statuses.rb
type Card::Statuses (line 1) | module Card::Statuses
function publish (line 14) | def publish
function mark_if_just_published (line 23) | def mark_if_just_published
FILE: app/models/card/taggable.rb
type Card::Taggable (line 1) | module Card::Taggable
function toggle_tag_with (line 11) | def toggle_tag_with(title)
function tagged_with? (line 23) | def tagged_with?(tag)
FILE: app/models/card/triageable.rb
type Card::Triageable (line 1) | module Card::Triageable
function triaged? (line 11) | def triaged?
function awaiting_triage? (line 15) | def awaiting_triage?
function triage_into (line 19) | def triage_into(column)
function send_back_to_triage (line 29) | def send_back_to_triage(skip_event: false)
FILE: app/models/card/watchable.rb
type Card::Watchable (line 1) | module Card::Watchable
function watched_by? (line 11) | def watched_by?(user)
function watch_for (line 15) | def watch_for(user)
function watch_by (line 19) | def watch_by(user)
function unwatch_by (line 23) | def unwatch_by(user)
function subscribe_creator (line 28) | def subscribe_creator
FILE: app/models/closure.rb
class Closure (line 1) | class Closure < ApplicationRecord
FILE: app/models/color.rb
class Color (line 3) | class Color
method for_value (line 5) | def for_value(value)
method to_s (line 10) | def to_s
FILE: app/models/column.rb
class Column (line 1) | class Column < ApplicationRecord
FILE: app/models/column/colored.rb
type Column::Colored (line 1) | module Column::Colored
function color (line 10) | def color
FILE: app/models/column/positioned.rb
type Column::Positioned (line 1) | module Column::Positioned
function move_left (line 10) | def move_left
function move_right (line 14) | def move_right
function left_column (line 18) | def left_column
function right_column (line 22) | def right_column
function leftmost? (line 26) | def leftmost?
function rightmost? (line 30) | def rightmost?
function adjacent_columns (line 34) | def adjacent_columns
function set_position (line 39) | def set_position
function swap_position_with (line 44) | def swap_position_with(other_column)
FILE: app/models/comment.rb
class Comment (line 1) | class Comment < ApplicationRecord
method to_partial_path (line 22) | def to_partial_path
method card_is_commentable (line 27) | def card_is_commentable
method watch_card_by_creator (line 31) | def watch_card_by_creator
FILE: app/models/comment/eventable.rb
type Comment::Eventable (line 1) | module Comment::Eventable
function event_was_created (line 10) | def event_was_created(event)
function should_track_event? (line 15) | def should_track_event?
function track_creation (line 19) | def track_creation
FILE: app/models/comment/mentions.rb
type Comment::Mentions (line 1) | module Comment::Mentions
function mentionable? (line 7) | def mentionable?
FILE: app/models/comment/promptable.rb
type Comment::Promptable (line 1) | module Comment::Promptable
function to_prompt (line 8) | def to_prompt
FILE: app/models/comment/searchable.rb
type Comment::Searchable (line 1) | module Comment::Searchable
function search_title (line 8) | def search_title
function search_content (line 12) | def search_content
function search_card_id (line 16) | def search_card_id
function search_board_id (line 20) | def search_board_id
function searchable? (line 24) | def searchable?
FILE: app/models/concerns/attachments.rb
type Attachments (line 1) | module Attachments
function attachments (line 20) | def attachments
function has_attachments? (line 24) | def has_attachments?
function remote_images (line 28) | def remote_images
function has_remote_images? (line 32) | def has_remote_images?
function remote_videos (line 36) | def remote_videos
function has_remote_videos? (line 40) | def has_remote_videos?
function rich_text_record (line 45) | def rich_text_record
FILE: app/models/concerns/eventable.rb
type Eventable (line 1) | module Eventable
function track_event (line 8) | def track_event(action, creator: Current.user, board: self.board, **pa...
function event_was_created (line 14) | def event_was_created(event)
function should_track_event? (line 18) | def should_track_event?
function eventable_prefix (line 22) | def eventable_prefix
FILE: app/models/concerns/filterable.rb
type Filterable (line 1) | module Filterable
function remove_from_filters (line 14) | def remove_from_filters
FILE: app/models/concerns/mentions.rb
type Mentions (line 1) | module Mentions
function create_mentions (line 10) | def create_mentions(mentioner: Current.user)
function mentionable_content (line 16) | def mentionable_content
function scan_mentionees (line 20) | def scan_mentionees
function mentionees_from_attachments (line 25) | def mentionees_from_attachments
function mentionable_users (line 29) | def mentionable_users
function rich_text_associations (line 33) | def rich_text_associations
function should_create_mentions? (line 37) | def should_create_mentions?
function mentionable_content_changed? (line 41) | def mentionable_content_changed?
function create_mentions_later (line 45) | def create_mentions_later
function mentionable? (line 50) | def mentionable?
function should_check_mentions? (line 54) | def should_check_mentions?
FILE: app/models/concerns/notifiable.rb
type Notifiable (line 1) | module Notifiable
function notify_recipients (line 10) | def notify_recipients
function notifiable_target (line 14) | def notifiable_target
function notify_recipients_later (line 19) | def notify_recipients_later
FILE: app/models/concerns/searchable.rb
type Searchable (line 1) | module Searchable
function reindex (line 12) | def reindex
function create_in_search_index (line 17) | def create_in_search_index
function update_in_search_index (line 23) | def update_in_search_index
function remove_from_search_index (line 31) | def remove_from_search_index
function search_record_attributes (line 35) | def search_record_attributes
function search_record_content (line 48) | def search_record_content
function search_record_class (line 52) | def search_record_class
FILE: app/models/concerns/storage/totaled.rb
type Storage::Totaled (line 1) | module Storage::Totaled
function foreign_key_for_storage (line 10) | def foreign_key_for_storage
function bytes_used (line 16) | def bytes_used
function bytes_used_exact (line 21) | def bytes_used_exact
function materialize_storage_later (line 25) | def materialize_storage_later
function materialize_storage (line 30) | def materialize_storage
function reconcile_storage (line 54) | def reconcile_storage
function create_or_find_storage_total (line 81) | def create_or_find_storage_total
function calculate_real_storage_bytes (line 85) | def calculate_real_storage_bytes
FILE: app/models/concerns/storage/tracked.rb
type Storage::Tracked (line 4) | module Storage::Tracked
function storage_tracked_record (line 12) | def storage_tracked_record
function board_for_storage_tracking (line 17) | def board_for_storage_tracking
function storage_bytes (line 22) | def storage_bytes
function board_transfer? (line 27) | def board_transfer?
function track_board_transfer (line 31) | def track_board_transfer
function storage_transfer_records (line 62) | def storage_transfer_records
function attachments_for_storage (line 67) | def attachments_for_storage(recordable = self)
function storage_attachments_for_records (line 71) | def storage_attachments_for_records(recordables)
FILE: app/models/current.rb
class Current (line 1) | class Current < ActiveSupport::CurrentAttributes
method session= (line 5) | def session=(value)
method identity= (line 13) | def identity=(identity)
method with_account (line 21) | def with_account(value, &)
method without_account (line 25) | def without_account(&)
FILE: app/models/entropy.rb
class Entropy (line 1) | class Entropy < ApplicationRecord
method auto_postpone_period_in_days (line 13) | def auto_postpone_period_in_days
method auto_postpone_period_in_days= (line 23) | def auto_postpone_period_in_days=(new_value)
method default_auto_postpone_period_in_days (line 28) | def default_auto_postpone_period_in_days
FILE: app/models/event.rb
class Event (line 1) | class Event < ApplicationRecord
method action (line 28) | def action
method notifiable_target (line 32) | def notifiable_target
method description_for (line 36) | def description_for(user)
method dispatch_webhooks (line 41) | def dispatch_webhooks
FILE: app/models/event/description.rb
class Event::Description (line 1) | class Event::Description
method initialize (line 7) | def initialize(event, user)
method to_html (line 12) | def to_html
method to_plain_text (line 16) | def to_plain_text
method to_sentence (line 21) | def to_sentence(creator, card_title)
method creator_tag (line 29) | def creator_tag
method card_title_tag (line 36) | def card_title_tag
method creator_name (line 40) | def creator_name
method quoted (line 44) | def quoted(text)
method card (line 48) | def card
method comment_sentence (line 52) | def comment_sentence(creator, card_title)
method action_sentence (line 56) | def action_sentence(creator, card_title)
method assigned_sentence (line 85) | def assigned_sentence(creator, card_title)
method unassigned_sentence (line 93) | def unassigned_sentence(creator, card_title)
method renamed_sentence (line 97) | def renamed_sentence(creator, card_title)
method moved_sentence (line 101) | def moved_sentence(creator, card_title)
method triaged_sentence (line 105) | def triaged_sentence(creator, card_title)
method assignee_names (line 109) | def assignee_names
method unassigned_names (line 113) | def unassigned_names
method old_title (line 117) | def old_title
method new_location (line 121) | def new_location
method column (line 125) | def column
FILE: app/models/event/particulars.rb
type Event::Particulars (line 1) | module Event::Particulars
function assignees (line 8) | def assignees
FILE: app/models/event/promptable.rb
type Event::Promptable (line 1) | module Event::Promptable
function to_prompt (line 4) | def to_prompt
FILE: app/models/export.rb
class Export (line 1) | class Export < ApplicationRecord
method cleanup (line 12) | def self.cleanup
method build_later (line 16) | def build_later
method build (line 20) | def build
method mark_completed (line 35) | def mark_completed
method accessible_to? (line 39) | def accessible_to?(accessor)
method filename (line 44) | def filename
method with_context (line 48) | def with_context
method populate_zip (line 59) | def populate_zip(zip)
FILE: app/models/filter.rb
class Filter (line 1) | class Filter < ApplicationRecord
method from_params (line 8) | def from_params(params)
method remember (line 12) | def remember(attrs)
method cards (line 19) | def cards
method empty? (line 43) | def empty?
method single_board (line 47) | def single_board
method single_workflow (line 51) | def single_workflow
method cacheable? (line 55) | def cacheable?
method cache_key (line 59) | def cache_key
method only_closed? (line 63) | def only_closed?
method include_closed_cards? (line 68) | def include_closed_cards?
method include_not_now_cards? (line 72) | def include_not_now_cards?
FILE: app/models/filter/fields.rb
type Filter::Fields (line 1) | module Filter::Fields
function default_values (line 10) | def default_values
function default_value? (line 14) | def default_value?(key, value)
function indexed_by_human_name (line 18) | def indexed_by_human_name(index)
function assignment_status (line 36) | def assignment_status
function indexed_by (line 40) | def indexed_by
function sorted_by (line 44) | def sorted_by
function creation_window (line 48) | def creation_window
function closure_window (line 52) | def closure_window
function terms (line 56) | def terms
function terms= (line 60) | def terms=(value)
function with (line 65) | def with(**fields)
function default_indexed_by (line 73) | def default_indexed_by
function default_indexed_by? (line 77) | def default_indexed_by?
function default_sorted_by (line 81) | def default_sorted_by
function default_sorted_by? (line 85) | def default_sorted_by?
FILE: app/models/filter/params.rb
type Filter::Params (line 1) | module Filter::Params
function find_by_params (line 20) | def find_by_params(params)
function digest_params (line 24) | def digest_params(params)
function normalize_params (line 28) | def normalize_params(params)
function used? (line 43) | def used?(ignore_boards: false)
function as_params (line 51) | def as_params
function as_params_without (line 68) | def as_params_without(key, value)
function params_digest (line 79) | def params_digest
FILE: app/models/filter/resources.rb
type Filter::Resources (line 1) | module Filter::Resources
function resource_removed (line 12) | def resource_removed(resource)
function boards (line 21) | def boards
function board_titles (line 25) | def board_titles
function boards_label (line 33) | def boards_label
FILE: app/models/filter/summarized.rb
type Filter::Summarized (line 1) | module Filter::Summarized
function summary (line 2) | def summary
function index_summary (line 7) | def index_summary
function sort_summary (line 13) | def sort_summary
function tag_summary (line 19) | def tag_summary
function assignee_summary (line 25) | def assignee_summary
function terms_summary (line 33) | def terms_summary
function creator_summary (line 39) | def creator_summary
FILE: app/models/identity.rb
class Identity (line 1) | class Identity < ApplicationRecord
method find_by_permissable_access_token (line 19) | def self.find_by_permissable_access_token(token, method:)
method send_magic_link (line 25) | def send_magic_link(**attributes)
method users_with_active_accounts (line 33) | def users_with_active_accounts
method deactivate_users (line 38) | def deactivate_users
FILE: app/models/identity/access_token.rb
class Identity::AccessToken (line 1) | class Identity::AccessToken < ApplicationRecord
method allows? (line 7) | def allows?(method)
FILE: app/models/identity/joinable.rb
type Identity::Joinable (line 1) | module Identity::Joinable
function join (line 4) | def join(account, **attributes)
FILE: app/models/identity/transferable.rb
type Identity::Transferable (line 1) | module Identity::Transferable
function find_by_transfer_id (line 7) | def find_by_transfer_id(id)
function transfer_id (line 12) | def transfer_id
FILE: app/models/magic_link.rb
class MagicLink (line 1) | class MagicLink < ApplicationRecord
method consume (line 18) | def consume(code)
method cleanup (line 22) | def cleanup
method consume (line 27) | def consume
method generate_code (line 33) | def generate_code
method set_expiration (line 40) | def set_expiration
FILE: app/models/magic_link/code.rb
type MagicLink::Code (line 1) | module MagicLink::Code
function generate (line 5) | def generate(length)
function sanitize (line 9) | def sanitize(code)
function normalize_code (line 18) | def normalize_code(code)
function apply_substitutions (line 22) | def apply_substitutions(code)
function remove_invalid_characters (line 26) | def remove_invalid_characters(code)
FILE: app/models/mention.rb
class Mention (line 1) | class Mention < ApplicationRecord
method self_mention? (line 13) | def self_mention?
method notifiable_target (line 17) | def notifiable_target
method watch_source_by_mentionee (line 22) | def watch_source_by_mentionee
FILE: app/models/notification.rb
class Notification (line 1) | class Notification < ApplicationRecord
method read_all (line 33) | def read_all
method unread_all (line 37) | def unread_all
method read (line 42) | def read
method unread (line 46) | def unread
method read? (line 50) | def read?
method set_card (line 55) | def set_card
method bundle (line 59) | def bundle
method broadcast_update (line 63) | def broadcast_update
FILE: app/models/notification/bundle.rb
class Notification::Bundle (line 1) | class Notification::Bundle < ApplicationRecord
method deliver_all (line 23) | def deliver_all
method deliver_all_later (line 30) | def deliver_all_later
method notifications (line 35) | def notifications
method deliver (line 39) | def deliver
method deliver_later (line 51) | def deliver_later
method flush (line 55) | def flush
method set_default_window (line 60) | def set_default_window
method window (line 66) | def window
method validate_no_overlapping (line 70) | def validate_no_overlapping
method deliverable? (line 76) | def deliverable?
method overlapping_bundles (line 80) | def overlapping_bundles
FILE: app/models/notification/default_payload.rb
class Notification::DefaultPayload (line 1) | class Notification::DefaultPayload
method initialize (line 6) | def initialize(notification)
method to_h (line 10) | def to_h
method title (line 14) | def title
method body (line 18) | def body
method url (line 22) | def url
method category (line 26) | def category
method high_priority? (line 30) | def high_priority?
method base_url (line 34) | def base_url
method avatar_url (line 38) | def avatar_url
method card_url (line 43) | def card_url(card)
method notifications_url (line 47) | def notifications_url
method url_options (line 51) | def url_options
FILE: app/models/notification/event_payload.rb
class Notification::EventPayload (line 1) | class Notification::EventPayload < Notification::DefaultPayload
method title (line 4) | def title
method body (line 13) | def body
method url (line 54) | def url
method category (line 63) | def category
method high_priority? (line 71) | def high_priority?
method event (line 76) | def event
method card_title (line 80) | def card_title
method event_particulars (line 84) | def event_particulars
method column_name (line 88) | def column_name
method new_location_name (line 92) | def new_location_name
method new_title (line 96) | def new_title
method card_url_with_comment_anchor (line 100) | def card_url_with_comment_anchor(comment)
FILE: app/models/notification/mention_payload.rb
class Notification::MentionPayload (line 1) | class Notification::MentionPayload < Notification::DefaultPayload
method title (line 4) | def title
method body (line 8) | def body
method url (line 12) | def url
method category (line 16) | def category
method high_priority? (line 20) | def high_priority?
method mention (line 25) | def mention
FILE: app/models/notification/push_target.rb
class Notification::PushTarget (line 1) | class Notification::PushTarget
method process (line 6) | def self.process(notification)
method initialize (line 10) | def initialize(notification)
method process (line 14) | def process
FILE: app/models/notification/push_target/web.rb
class Notification::PushTarget::Web (line 1) | class Notification::PushTarget::Web < Notification::PushTarget
method process (line 2) | def process
method subscriptions (line 9) | def subscriptions
FILE: app/models/notification/pushable.rb
type Notification::Pushable (line 1) | module Notification::Pushable
function register_push_target (line 11) | def register_push_target(target)
function resolve_push_target (line 17) | def resolve_push_target(target)
function push_later (line 26) | def push_later
function push (line 30) | def push
function payload (line 36) | def payload
function pushable? (line 41) | def pushable?
function push_to (line 45) | def push_to(target)
function payload_type (line 49) | def payload_type
FILE: app/models/notifier.rb
class Notifier (line 1) | class Notifier
method for (line 5) | def for(source)
method notify (line 15) | def notify
method initialize (line 41) | def initialize(source)
method should_notify? (line 45) | def should_notify?
FILE: app/models/notifier/card_event_notifier.rb
class Notifier::CardEventNotifier (line 1) | class Notifier::CardEventNotifier < Notifier
method recipients (line 6) | def recipients
method card (line 19) | def card
FILE: app/models/notifier/comment_event_notifier.rb
class Notifier::CommentEventNotifier (line 1) | class Notifier::CommentEventNotifier < Notifier
method recipients (line 5) | def recipients
method card (line 9) | def card
FILE: app/models/notifier/mention_notifier.rb
class Notifier::MentionNotifier (line 1) | class Notifier::MentionNotifier < Notifier
method recipients (line 5) | def recipients
method creator (line 13) | def creator
FILE: app/models/passkey/authenticator.rb
class Passkey::Authenticator (line 1) | class Passkey::Authenticator < Data.define(:aaguids, :name, :icon)
method find_by_aaguid (line 3) | def find_by_aaguid(aaguid)
method registry (line 7) | def registry
method all (line 17) | def all
FILE: app/models/pin.rb
class Pin (line 1) | class Pin < ApplicationRecord
FILE: app/models/push.rb
type Push (line 1) | module Push
function table_name_prefix (line 2) | def self.table_name_prefix
FILE: app/models/push/subscription.rb
class Push::Subscription (line 1) | class Push::Subscription < ApplicationRecord
method notification (line 16) | def notification(**params)
method resolved_endpoint_ip (line 27) | def resolved_endpoint_ip
method endpoint_uri (line 33) | def endpoint_uri
method validate_endpoint_url (line 39) | def validate_endpoint_url
method permitted_endpoint_host? (line 51) | def permitted_endpoint_host?
FILE: app/models/qr_code_link.rb
class QrCodeLink (line 1) | class QrCodeLink
method from_signed (line 5) | def from_signed(signed)
method verifier (line 9) | def verifier
method secret (line 14) | def secret
method initialize (line 19) | def initialize(url)
method signed (line 23) | def signed
FILE: app/models/reaction.rb
class Reaction (line 1) | class Reaction < ApplicationRecord
method register_card_activity (line 13) | def register_card_activity
FILE: app/models/search.rb
type Search (line 1) | module Search
function table_name_prefix (line 2) | def self.table_name_prefix
FILE: app/models/search/highlighter.rb
class Search::Highlighter (line 1) | class Search::Highlighter
method initialize (line 8) | def initialize(query)
method highlight (line 12) | def highlight(text)
method snippet (line 24) | def snippet(text, max_words: 20)
method terms (line 45) | def terms
method escape_highlight_marks (line 62) | def escape_highlight_marks(html)
FILE: app/models/search/query.rb
class Search::Query (line 1) | class Search::Query < ApplicationRecord
method wrap (line 11) | def wrap(query)
method sanitize_terms (line 21) | def sanitize_terms
method sanitize (line 25) | def sanitize(terms)
method remove_invalid_search_characters (line 35) | def remove_invalid_search_characters(terms)
method remove_unbalanced_quotes (line 39) | def remove_unbalanced_quotes(terms)
FILE: app/models/search/record.rb
class Search::Record (line 1) | class Search::Record < ApplicationRecord
method upsert! (line 10) | def upsert!(attributes)
method card_join (line 20) | def card_join
method source (line 44) | def source
method comment (line 48) | def comment
FILE: app/models/search/record/sqlite.rb
type Search::Record::SQLite (line 1) | module Search::Record::SQLite
function search_fields (line 20) | def search_fields(query)
function for (line 30) | def for(account_id)
function card_title (line 35) | def card_title
function card_description (line 39) | def card_description
function comment_body (line 43) | def comment_body
function escape_fts_highlight (line 48) | def escape_fts_highlight(html)
function upsert_to_fts5_table (line 57) | def upsert_to_fts5_table
FILE: app/models/search/record/sqlite/fts.rb
class Search::Record::SQLite::Fts (line 1) | class Search::Record::SQLite::Fts < ApplicationRecord
method upsert (line 14) | def self.upsert(rowid, title, content)
FILE: app/models/search/record/trilogy.rb
type Search::Record::Trilogy (line 1) | module Search::Record::Trilogy
function name (line 19) | def self.name
function shard_id_for_account (line 27) | def shard_id_for_account(account_id)
function search_fields (line 31) | def search_fields(query)
function for (line 35) | def for(account_id)
function card_title (line 40) | def card_title
function card_description (line 44) | def card_description
function comment_body (line 48) | def comment_body
function stem_content (line 53) | def stem_content
function set_account_key (line 58) | def set_account_key
function highlight (line 62) | def highlight(text, show:)
FILE: app/models/search/result.rb
class Search::Result (line 1) | class Search::Result < ApplicationRecord
method card_title (line 10) | def card_title
method card_description (line 14) | def card_description
method comment_body (line 18) | def comment_body
method source (line 22) | def source
method readonly? (line 26) | def readonly?
method highlight (line 31) | def highlight(text, show:)
FILE: app/models/search/stemmer.rb
type Search::Stemmer (line 1) | module Search::Stemmer
function stem (line 6) | def stem(value)
FILE: app/models/session.rb
class Session (line 1) | class Session < ApplicationRecord
FILE: app/models/signup.rb
class Signup (line 1) | class Signup
method initialize (line 13) | def initialize(...)
method create_identity (line 19) | def create_identity
method complete (line 24) | def complete
method create_tenant (line 48) | def create_tenant
method handle_account_creation_error (line 53) | def handle_account_creation_error(error)
method create_account (line 56) | def create_account
method generate_account_name (line 71) | def generate_account_name
method destroy_account (line 76) | def destroy_account
method subscription_attributes (line 84) | def subscription_attributes
method request_attributes (line 93) | def request_attributes
FILE: app/models/signup/account_name_generator.rb
class Signup::AccountNameGenerator (line 1) | class Signup::AccountNameGenerator
method initialize (line 6) | def initialize(identity:, name:)
method generate (line 11) | def generate
method current_index (line 22) | def current_index
method existing_indices (line 26) | def existing_indices
method first_account_name_regex (line 38) | def first_account_name_regex
method nth_account_name_regex (line 42) | def nth_account_name_regex
method prefix (line 46) | def prefix
method first_name (line 50) | def first_name
FILE: app/models/ssrf_protection.rb
type SsrfProtection (line 1) | module SsrfProtection
function resolve_public_ip (line 17) | def resolve_public_ip(hostname)
function blocked_address? (line 23) | def blocked_address?(ip)
function resolve_dns (line 35) | def resolve_dns(hostname)
function in_disallowed_range? (line 47) | def in_disallowed_range?(ip)
FILE: app/models/step.rb
class Step (line 1) | class Step < ApplicationRecord
method completed? (line 9) | def completed?
FILE: app/models/storage.rb
type Storage (line 1) | module Storage
function table_name_prefix (line 2) | def self.table_name_prefix
FILE: app/models/storage/attachment_tracking.rb
type Storage::AttachmentTracking (line 1) | module Storage::AttachmentTracking
function record_storage_attach (line 13) | def record_storage_attach
function record_storage_detach (line 25) | def record_storage_detach
function snapshot_storage_context (line 39) | def snapshot_storage_context
function storage_tracked_record (line 49) | def storage_tracked_record
FILE: app/models/storage/entry.rb
class Storage::Entry (line 1) | class Storage::Entry < ApplicationRecord
method record (line 11) | def self.record(delta:, operation:, account:, board: nil, recordable: ...
FILE: app/models/storage/total.rb
class Storage::Total (line 1) | class Storage::Total < ApplicationRecord
method pending_entries (line 4) | def pending_entries
method current_usage (line 9) | def current_usage
FILE: app/models/tag.rb
class Tag (line 1) | class Tag < ApplicationRecord
method hashtag (line 14) | def hashtag
method cards_count (line 18) | def cards_count
FILE: app/models/tag/attachable.rb
type Tag::Attachable (line 1) | module Tag::Attachable
function attachable_plain_text_representation (line 7) | def attachable_plain_text_representation(...)
FILE: app/models/tagging.rb
class Tagging (line 1) | class Tagging < ApplicationRecord
FILE: app/models/time_window_parser.rb
class TimeWindowParser (line 1) | class TimeWindowParser
method parse (line 18) | def parse(string)
method human_name_for (line 22) | def human_name_for(value)
method initialize (line 27) | def initialize(now: Time.current)
method parse (line 31) | def parse(string)
method normalize (line 53) | def normalize(string)
FILE: app/models/user.rb
class User (line 1) | class User < ApplicationRecord
method deactivate (line 19) | def deactivate
method setup? (line 27) | def setup?
method verified? (line 31) | def verified?
method verify (line 35) | def verify
method close_remote_connections (line 40) | def close_remote_connections
FILE: app/models/user/accessor.rb
type User::Accessor (line 1) | module User::Accessor
function draft_new_card_in (line 14) | def draft_new_card_in(board)
function grant_access_to_boards (line 21) | def grant_access_to_boards
FILE: app/models/user/assignee.rb
type User::Assignee (line 1) | module User::Assignee
FILE: app/models/user/attachable.rb
type User::Attachable (line 1) | module User::Attachable
function attachable_plain_text_representation (line 7) | def attachable_plain_text_representation(...)
FILE: app/models/user/avatar.rb
type User::Avatar (line 3) | module User::Avatar
function avatar_attached? (line 23) | def avatar_attached?
function avatar_thumbnail (line 27) | def avatar_thumbnail
function avatar_background_color (line 31) | def avatar_background_color
function publicly_accessible? (line 36) | def publicly_accessible?
function avatar_content_type_allowed (line 41) | def avatar_content_type_allowed
function avatar_dimensions_allowed (line 47) | def avatar_dimensions_allowed
FILE: app/models/user/configurable.rb
type User::Configurable (line 1) | module User::Configurable
function in_time_zone (line 13) | def in_time_zone(&block)
FILE: app/models/user/data_export.rb
class User::DataExport (line 1) | class User::DataExport < Export
method filename (line 3) | def filename
method populate_zip (line 7) | def populate_zip(zip)
method exportable_cards (line 13) | def exportable_cards
method add_card_to_zip (line 22) | def add_card_to_zip(zip, card)
FILE: app/models/user/day_timeline.rb
class User::DayTimeline (line 1) | class User::DayTimeline
method initialize (line 8) | def initialize(user, day, filter)
method has_activity? (line 12) | def has_activity?
method events (line 16) | def events
method next_day (line 20) | def next_day
method earliest_time (line 24) | def earliest_time
method latest_time (line 28) | def latest_time
method added_column (line 32) | def added_column
method updated_column (line 36) | def updated_column
method closed_column (line 40) | def closed_column
method cache_key (line 44) | def cache_key
method filtered_events (line 64) | def filtered_events
method timelineable_events (line 72) | def timelineable_events
method boards (line 79) | def boards
method latest_event_before (line 83) | def latest_event_before
method build_column (line 87) | def build_column(id, base_title, index, events)
method window (line 91) | def window
FILE: app/models/user/day_timeline/column.rb
class User::DayTimeline::Column (line 1) | class User::DayTimeline::Column
method initialize (line 6) | def initialize(day_timeline, id, base_title, index, events)
method title (line 14) | def title
method events_by_hour (line 21) | def events_by_hour
method has_more_events? (line 25) | def has_more_events?
method hidden_events_count (line 29) | def hidden_events_count
method to_param (line 33) | def to_param
method limited_events (line 38) | def limited_events
method full_events_count (line 42) | def full_events_count
FILE: app/models/user/day_timeline/serializable.rb
type User::DayTimeline::Serializable (line 1) | module User::DayTimeline::Serializable
function find (line 10) | def find(id)
function tenanted? (line 19) | def tenanted?
function as_json (line 25) | def as_json(options = {})
FILE: app/models/user/email_address_changeable.rb
type User::EmailAddressChangeable (line 1) | module User::EmailAddressChangeable
function change_email_address_using_token (line 7) | def change_email_address_using_token(token)
function send_email_address_change_confirmation (line 20) | def send_email_address_change_confirmation(new_email_address)
function change_email_address (line 33) | def change_email_address(new_email_address)
function generate_email_address_change_token (line 41) | def generate_email_address_change_token(from: identity.email_address, ...
FILE: app/models/user/filtering.rb
class User::Filtering (line 1) | class User::Filtering
method initialize (line 7) | def initialize(user, filter, expanded: false)
method boards (line 11) | def boards
method selected_board_titles (line 15) | def selected_board_titles
method selected_boards_label (line 19) | def selected_boards_label
method tags (line 23) | def tags
method users (line 27) | def users
method filters (line 31) | def filters
method expanded? (line 35) | def expanded?
method any? (line 39) | def any?
method show_indexed_by? (line 43) | def show_indexed_by?
method show_sorted_by? (line 47) | def show_sorted_by?
method show_tags? (line 51) | def show_tags?
method show_assignees? (line 56) | def show_assignees?
method show_creators? (line 60) | def show_creators?
method show_closers? (line 64) | def show_closers?
method show_boards? (line 68) | def show_boards?
method single_board_or_first (line 72) | def single_board_or_first
method cache_key (line 77) | def cache_key
method account (line 82) | def account
FILE: app/models/user/mentionable.rb
type User::Mentionable (line 1) | module User::Mentionable
function to_attachable_partial_path (line 8) | def to_attachable_partial_path
function mentioned_by (line 13) | def mentioned_by(mentioner, at:)
function mentionable_handles (line 17) | def mentionable_handles
function content_type (line 21) | def content_type
function first_name_with_last_name_initial (line 26) | def first_name_with_last_name_initial
FILE: app/models/user/named.rb
type User::Named (line 1) | module User::Named
function first_name (line 8) | def first_name
function last_name (line 12) | def last_name
function initials (line 16) | def initials
function familiar_name (line 20) | def familiar_name
FILE: app/models/user/notifiable.rb
type User::Notifiable (line 1) | module User::Notifiable
function bundle (line 11) | def bundle(notification)
function find_or_create_bundle_for (line 18) | def find_or_create_bundle_for(notification)
function find_bundle_for (line 22) | def find_bundle_for(notification)
function expand_pending_bundle_for (line 26) | def expand_pending_bundle_for(notification)
function create_bundle_for (line 33) | def create_bundle_for(notification)
FILE: app/models/user/role.rb
type User::Role (line 1) | module User::Role
function admin? (line 12) | def admin?
function can_change? (line 17) | def can_change?(other)
function can_administer? (line 21) | def can_administer?(other)
function can_administer_board? (line 25) | def can_administer_board?(board)
function can_administer_card? (line 29) | def can_administer_card?(card)
FILE: app/models/user/searcher.rb
type User::Searcher (line 1) | module User::Searcher
function search (line 8) | def search(terms)
function remember_search (line 12) | def remember_search(terms)
FILE: app/models/user/settings.rb
class User::Settings (line 1) | class User::Settings < ApplicationRecord
method bundle_aggregation_period (line 10) | def bundle_aggregation_period
method bundling_emails? (line 23) | def bundling_emails?
method timezone (line 27) | def timezone
method review_pending_bundles (line 36) | def review_pending_bundles
method cancel_pending_bundles (line 44) | def cancel_pending_bundles
method flush_pending_bundles (line 50) | def flush_pending_bundles
method default_timezone (line 54) | def default_timezone
FILE: app/models/user/timelined.rb
type User::Timelined (line 1) | module User::Timelined
function timeline_for (line 8) | def timeline_for(day, filter:)
FILE: app/models/user/transferable.rb
type User::Transferable (line 1) | module User::Transferable
function find_by_transfer_id (line 7) | def find_by_transfer_id(id)
function transfer_id (line 12) | def transfer_id
FILE: app/models/user/watcher.rb
type User::Watcher (line 1) | module User::Watcher
FILE: app/models/watch.rb
class Watch (line 1) | class Watch < ApplicationRecord
FILE: app/models/webhook.rb
class Webhook (line 1) | class Webhook < ApplicationRecord
method activate (line 44) | def activate
method deactivate (line 48) | def deactivate
method renderer (line 52) | def renderer
method for_basecamp? (line 56) | def for_basecamp?
method for_campfire? (line 60) | def for_campfire?
method for_slack? (line 64) | def for_slack?
method validate_url (line 69) | def validate_url
FILE: app/models/webhook/delinquency_tracker.rb
class Webhook::DelinquencyTracker (line 1) | class Webhook::DelinquencyTracker < ApplicationRecord
method record_delivery_of (line 8) | def record_delivery_of(delivery)
method reset (line 20) | def reset
method mark_first_failure_time (line 24) | def mark_first_failure_time
method delinquent? (line 28) | def delinquent?
method failing_for_too_long? (line 32) | def failing_for_too_long?
method too_many_consecutive_failures? (line 40) | def too_many_consecutive_failures?
FILE: app/models/webhook/delivery.rb
class Webhook::Delivery (line 1) | class Webhook::Delivery < ApplicationRecord
class ResponseTooLarge (line 4) | class ResponseTooLarge < StandardError; end
method cleanup (line 25) | def self.cleanup(batch_size: 500, pause: 0.1)
method deliver_later (line 29) | def deliver_later
method deliver (line 33) | def deliver
method failed? (line 47) | def failed?
method succeeded? (line 51) | def succeeded?
method perform_request (line 56) | def perform_request
method stream_body_with_limit (line 80) | def stream_body_with_limit(response)
method resolved_ip (line 88) | def resolved_ip
method uri (line 93) | def uri
method http (line 97) | def http
method headers (line 106) | def headers
method signature (line 115) | def signature
method content_type (line 119) | def content_type
method payload (line 129) | def payload
method render_payload (line 141) | def render_payload(**options)
method convert_html_to_mrkdwn (line 145) | def convert_html_to_mrkdwn(html)
method slack_payload (line 163) | def slack_payload
method base_url_options (line 170) | def base_url_options
FILE: app/models/webhook/triggerable.rb
type Webhook::Triggerable (line 1) | module Webhook::Triggerable
function trigger (line 9) | def trigger(event)
FILE: app/models/zip_file.rb
class ZipFile (line 1) | class ZipFile
class InvalidFileError (line 2) | class InvalidFileError < StandardError; end
method create_for (line 5) | def create_for(attachment, filename:)
method read_from (line 19) | def read_from(blob)
method s3_service? (line 30) | def s3_service?(service)
method create_for_s3 (line 35) | def create_for_s3(attachment, filename:, service:)
method create_for_disk (line 70) | def create_for_disk(attachment, filename:)
method read_from_s3 (line 85) | def read_from_s3(blob)
method read_from_disk (line 93) | def read_from_disk(blob)
FILE: app/models/zip_file/reader.rb
class ZipFile::Reader (line 1) | class ZipFile::Reader
method initialize (line 2) | def initialize(io)
method read (line 9) | def read(file_path)
method glob (line 21) | def glob(pattern)
method exists? (line 25) | def exists?(file_path)
FILE: app/models/zip_file/reader/io.rb
class ZipFile::Reader::IO (line 1) | class ZipFile::Reader::IO
method initialize (line 2) | def initialize(entry, io)
method read (line 8) | def read(length = nil, buffer = nil)
method eof? (line 22) | def eof?
method rewind (line 26) | def rewind
method size (line 31) | def size
FILE: app/models/zip_file/remote_io.rb
class ZipFile::RemoteIO (line 1) | class ZipFile::RemoteIO < ZipKit::RemoteIO
method initialize (line 2) | def initialize(url, ssl_verify_peer: true)
method request_range (line 8) | def request_range(range)
method request_object_size (line 23) | def request_object_size
method with_http (line 42) | def with_http
FILE: app/models/zip_file/writer.rb
class ZipFile::Writer (line 1) | class ZipFile::Writer
method initialize (line 4) | def initialize(io = nil)
method stream_to (line 12) | def stream_to(io)
method write (line 16) | def write(data)
method add_file (line 23) | def add_file(path, content = nil, compress: true)
method glob (line 34) | def glob(pattern)
method exists? (line 38) | def exists?(path)
method close (line 42) | def close
method checksum (line 46) | def checksum
method streamer (line 51) | def streamer
FILE: config/application.rb
type Fizzy (line 8) | module Fizzy
class Application (line 9) | class Application < Rails::Application
FILE: config/initializers/action_text.rb
type ActionText (line 1) | module ActionText
type Extensions (line 2) | module Extensions
type RichText (line 3) | module RichText
function storage_tracked_record (line 16) | def storage_tracked_record
function accessible_to? (line 20) | def accessible_to?(user)
function publicly_accessible? (line 24) | def publicly_accessible?
FILE: config/initializers/active_job.rb
type FizzyActiveJobExtensions (line 4) | module FizzyActiveJobExtensions
function initialize (line 12) | def initialize(...)
function serialize (line 17) | def serialize
function deserialize (line 21) | def deserialize(job_data)
function perform_now (line 28) | def perform_now
FILE: config/initializers/active_storage.rb
function to_rich_text_attributes (line 16) | def to_rich_text_attributes(*)
type ActiveStorageControllerExtensions (line 33) | module ActiveStorageControllerExtensions
type ActiveStorageDirectUploadsControllerExtensions (line 49) | module ActiveStorageDirectUploadsControllerExtensions
FILE: config/initializers/active_storage_no_reuse.rb
function blob_account_matches_record (line 33) | def blob_account_matches_record
function no_tracked_blob_reuse (line 42) | def no_tracked_blob_reuse
function whitelisted_for_cross_account? (line 62) | def whitelisted_for_cross_account?
FILE: config/initializers/active_storage_purge_on_last_attachment.rb
type ActiveStorage (line 5) | module ActiveStorage
type PurgeOnLastAttachment (line 6) | module PurgeOnLastAttachment
function purge (line 7) | def purge
function purge_later (line 15) | def purge_later
function purge_dependent_blob_later (line 24) | def purge_dependent_blob_later
function purge_blob_if_last (line 30) | def purge_blob_if_last(mode)
FILE: config/initializers/database_role_logging.rb
class DatabaseRoleLogger (line 3) | class DatabaseRoleLogger
method initialize (line 4) | def initialize(app)
method call (line 8) | def call(env)
FILE: config/initializers/sqlite_schema_dumper.rb
type SQLiteFTS5SchemaDumperFix (line 4) | module SQLiteFTS5SchemaDumperFix
function virtual_tables (line 6) | def virtual_tables(stream)
FILE: config/initializers/table_definition_column_limits.rb
type TableDefinitionColumnLimits (line 12) | module TableDefinitionColumnLimits
function column (line 22) | def column(name, type, **options)
type SQLiteColumnLimitCheckConstraints (line 44) | module SQLiteColumnLimitCheckConstraints
function add_column_options! (line 45) | def add_column_options!(sql, options)
FILE: config/initializers/tenanting/account_slug.rb
type AccountSlug (line 1) | module AccountSlug
class Extractor (line 5) | class Extractor
method initialize (line 6) | def initialize(app)
method call (line 12) | def call(env)
function decode (line 41) | def self.decode(slug) slug.to_i end
function encode (line 42) | def self.encode(id) id.to_s end
FILE: config/initializers/tenanting/turbo.rb
type TurboStreamsJobExtensions (line 1) | module TurboStreamsJobExtensions
function render_format (line 5) | def render_format(format, **rendering)
FILE: config/initializers/uuid_primary_keys.rb
type UuidPrimaryKeyDefault (line 3) | module UuidPrimaryKeyDefault
function load_schema! (line 4) | def load_schema!
function define_uuid_primary_key_pending_default (line 10) | def define_uuid_primary_key_pending_default
function uuid_primary_key? (line 18) | def uuid_primary_key?
function apply_to (line 23) | def apply_to(attribute_set)
type MysqlUuidAdapter (line 29) | module MysqlUuidAdapter
function lookup_cast_type (line 33) | def lookup_cast_type(sql_type)
function fetch_type_metadata (line 42) | def fetch_type_metadata(sql_type, extra = "")
function native_database_types (line 56) | def native_database_types
type SqliteUuidAdapter (line 62) | module SqliteUuidAdapter
function lookup_cast_type (line 66) | def lookup_cast_type(sql_type)
function fetch_type_metadata (line 75) | def fetch_type_metadata(sql_type)
function native_database_types (line 88) | def native_database_types
type SchemaDumperUuidType (line 94) | module SchemaDumperUuidType
function schema_type (line 96) | def schema_type(column)
type TableDefinitionUuidSupport (line 105) | module TableDefinitionUuidSupport
function uuid (line 106) | def uuid(name, **options)
FILE: config/initializers/web_push.rb
type WebPush::PersistentRequest (line 18) | module WebPush::PersistentRequest
function perform (line 19) | def perform
FILE: db/migrate/20251111122540_initial_schema.rb
class InitialSchema (line 1) | class InitialSchema < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251111153019_add_number_to_cards.rb
class AddNumberToCards (line 1) | class AddNumberToCards < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251112093037_create_search_indices.rb
class CreateSearchIndices (line 1) | class CreateSearchIndices < ActiveRecord::Migration[8.2]
method up (line 2) | def up
method down (line 22) | def down
FILE: db/migrate/20251112184932_remove_join_code_from_memberships.rb
class RemoveJoinCodeFromMemberships (line 1) | class RemoveJoinCodeFromMemberships < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251113111501_drop_memberships.rb
class DropMemberships (line 1) | class DropMemberships < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251113160907_add_missing_account_id_columns.rb
class AddMissingAccountIdColumns (line 1) | class AddMissingAccountIdColumns < ActiveRecord::Migration[8.2]
method change (line 45) | def change
FILE: db/migrate/20251113163145_ensure_account_id_index.rb
class EnsureAccountIdIndex (line 1) | class EnsureAccountIdIndex < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251113190256_create_search_record_shards.rb
class CreateSearchRecordShards (line 1) | class CreateSearchRecordShards < ActiveRecord::Migration[8.2]
method change (line 4) | def change
FILE: db/migrate/20251114084325_drop_search_results.rb
class DropSearchResults (line 1) | class DropSearchResults < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251114183203_ensure_an_identit_can_only_have_one_user_in_an_account.rb
class EnsureAnIdentitCanOnlyHaveOneUserInAnAccount (line 1) | class EnsureAnIdentitCanOnlyHaveOneUserInAnAccount < ActiveRecord::Migra...
method change (line 2) | def change
FILE: db/migrate/20251117190817_change_endpoint_to_text_in_push_subscriptions.rb
class ChangeEndpointToTextInPushSubscriptions (line 1) | class ChangeEndpointToTextInPushSubscriptions < ActiveRecord::Migration[...
method change (line 2) | def change
FILE: db/migrate/20251117192434_change_external_account_id_to_bigint_in_accounts.rb
class ChangeExternalAccountIdToBigintInAccounts (line 1) | class ChangeExternalAccountIdToBigintInAccounts < ActiveRecord::Migratio...
method change (line 2) | def change
FILE: db/migrate/20251117202517_change_usage_limit_to_bigint_in_account_join_codes.rb
class ChangeUsageLimitToBigintInAccountJoinCodes (line 1) | class ChangeUsageLimitToBigintInAccountJoinCodes < ActiveRecord::Migrati...
method change (line 2) | def change
FILE: db/migrate/20251120110206_add_search_records.rb
class AddSearchRecords (line 1) | class AddSearchRecords < ActiveRecord::Migration[8.2]
method up (line 2) | def up
method down (line 31) | def down
FILE: db/migrate/20251120194700_remove_all_foreign_key_constraints.rb
class RemoveAllForeignKeyConstraints (line 1) | class RemoveAllForeignKeyConstraints < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251120203100_add_unique_index_to_card_activity_spikes_on_card_id.rb
class AddUniqueIndexToCardActivitySpikesOnCardId (line 1) | class AddUniqueIndexToCardActivitySpikesOnCardId < ActiveRecord::Migrati...
method change (line 2) | def change
FILE: db/migrate/20251121092508_add_account_key_to_search_records.rb
class AddAccountKeyToSearchRecords (line 1) | class AddAccountKeyToSearchRecords < ActiveRecord::Migration[8.2]
method up (line 2) | def up
method down (line 13) | def down
FILE: db/migrate/20251121112416_remove_old_fulltext_indexes_from_search_records.rb
class RemoveOldFulltextIndexesFromSearchRecords (line 1) | class RemoveOldFulltextIndexesFromSearchRecords < ActiveRecord::Migratio...
method up (line 2) | def up
method down (line 10) | def down
FILE: db/migrate/20251125110629_increase_user_agent_length.rb
class IncreaseUserAgentLength (line 1) | class IncreaseUserAgentLength < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251125130010_add_a_staff_flag_to_identities.rb
class AddAStaffFlagToIdentities (line 1) | class AddAStaffFlagToIdentities < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251127000001_create_account_external_id_sequences.rb
class CreateAccountExternalIdSequences (line 1) | class CreateAccountExternalIdSequences < ActiveRecord::Migration[8.0]
method change (line 2) | def change
FILE: db/migrate/20251129110120_add_purpose_to_magic_links.rb
class AddPurposeToMagicLinks (line 1) | class AddPurposeToMagicLinks < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251129175717_promote_first_admin_to_owner.rb
class PromoteFirstAdminToOwner (line 1) | class PromoteFirstAdminToOwner < ActiveRecord::Migration[8.2]
method up (line 2) | def up
method down (line 11) | def down
FILE: db/migrate/20251201100607_create_account_exports.rb
class CreateAccountExports (line 1) | class CreateAccountExports < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251201132341_create_identity_access_tokens.rb
class CreateIdentityAccessTokens (line 1) | class CreateIdentityAccessTokens < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251205010536_add_verified_at_to_users.rb
class AddVerifiedAtToUsers (line 1) | class AddVerifiedAtToUsers < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251205205826_create_storage_tables.rb
class CreateStorageTables (line 1) | class CreateStorageTables < ActiveRecord::Migration[8.0]
method change (line 2) | def change
FILE: db/migrate/20251210054934_add_blob_id_and_audit_context_to_storage_entries.rb
class AddBlobIdAndAuditContextToStorageEntries (line 1) | class AddBlobIdAndAuditContextToStorageEntries < ActiveRecord::Migration...
method change (line 2) | def change
FILE: db/migrate/20251219120755_drop_card_engagements.rb
class DropCardEngagements (line 1) | class DropCardEngagements < ActiveRecord::Migration[8.2]
method up (line 2) | def up
method down (line 6) | def down
FILE: db/migrate/20251223000001_rename_account_exports_to_exports.rb
class RenameAccountExportsToExports (line 1) | class RenameAccountExportsToExports < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251223000002_create_account_imports.rb
class CreateAccountImports (line 1) | class CreateAccountImports < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20251224092315_create_account_cancellations.rb
class CreateAccountCancellations (line 1) | class CreateAccountCancellations < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20260121155752_make_reactions_polymorphic.rb
class MakeReactionsPolymorphic (line 1) | class MakeReactionsPolymorphic < ActiveRecord::Migration[8.0]
method change (line 2) | def change
FILE: db/migrate/20260206104338_add_card_id_to_notifications.rb
class AddCardIdToNotifications (line 1) | class AddCardIdToNotifications < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20260209165805_notifications_data_migration.rb
class NotificationsDataMigration (line 1) | class NotificationsDataMigration < ActiveRecord::Migration[8.2]
class Notification (line 4) | class Notification < ActiveRecord::Base
method change (line 8) | def change
method populate_card_id (line 21) | def populate_card_id
method collapse_duplicates (line 51) | def collapse_duplicates
FILE: db/migrate/20260211122517_add_failure_reason_to_account_imports.rb
class AddFailureReasonToAccountImports (line 1) | class AddFailureReasonToAccountImports < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20260212102026_fix_notifications_ordered_index.rb
class FixNotificationsOrderedIndex (line 1) | class FixNotificationsOrderedIndex < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20260213154740_create_action_pack_passkeys.rb
class CreateActionPackPasskeys (line 1) | class CreateActionPackPasskeys < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20260213170100_add_created_at_index_to_webhook_deliveries.rb
class AddCreatedAtIndexToWebhookDeliveries (line 1) | class AddCreatedAtIndexToWebhookDeliveries < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: db/migrate/20260218120000_restore_unique_index_on_board_publication_key.rb
class RestoreUniqueIndexOnBoardPublicationKey (line 1) | class RestoreUniqueIndexOnBoardPublicationKey < ActiveRecord::Migration[...
method change (line 2) | def change
FILE: db/seeds.rb
function seed_account (line 8) | def seed_account(name)
function create_tenant (line 14) | def create_tenant(signal_account_name)
function find_or_create_user (line 34) | def find_or_create_user(full_name, email_address)
function login_as (line 43) | def login_as(user)
function create_board (line 47) | def create_board(name, creator: Current.user, all_access: true, access_t...
function create_card (line 51) | def create_card(title, board:, description: nil, status: :published, cre...
FILE: lib/action_pack/passkey.rb
class ActionPack::Passkey (line 30) | class ActionPack::Passkey < Rails.configuration.action_pack.passkey.pare...
method creation_options (line 40) | def creation_options(holder:, **options)
method register (line 56) | def register(passkey, challenge: ActionPack::WebAuthn::Current.challen...
method request_options (line 66) | def request_options(holder: nil, **options)
method authenticate (line 77) | def authenticate(passkey, challenge: ActionPack::WebAuthn::Current.cha...
method authenticate (line 85) | def authenticate(passkey, challenge: ActionPack::WebAuthn::Current.cha...
method to_public_key_credential (line 96) | def to_public_key_credential
FILE: lib/action_pack/passkey/challenges_controller.rb
class ActionPack::Passkey::ChallengesController (line 17) | class ActionPack::Passkey::ChallengesController < ActionController::Base
method create (line 24) | def create
method create_passkey_challenge (line 32) | def create_passkey_challenge
FILE: lib/action_pack/passkey/form_helper.rb
type ActionPack::Passkey::FormHelper (line 10) | module ActionPack::Passkey::FormHelper
function passkey_creation_options_meta_tag (line 14) | def passkey_creation_options_meta_tag(creation_options, challenge_url:...
function passkey_request_options_meta_tag (line 22) | def passkey_request_options_meta_tag(request_options, challenge_url: nil)
function passkey_creation_button (line 36) | def passkey_creation_button(name = nil, url = nil, param: :passkey, fo...
function passkey_sign_in_button (line 59) | def passkey_sign_in_button(name = nil, url = nil, param: :passkey, med...
function passkey_challenge_url_meta_tag (line 76) | def passkey_challenge_url_meta_tag(challenge_url: nil)
function default_passkey_challenge_url (line 80) | def default_passkey_challenge_url
FILE: lib/action_pack/passkey/holder.rb
type ActionPack::Passkey::Holder (line 37) | module ActionPack::Passkey::Holder
function has_passkeys (line 48) | def has_passkeys(**options, &block)
class Config (line 72) | class Config
method initialize (line 75) | def initialize(**options)
method request_options (line 91) | def request_options(&block)
method creation_options (line 98) | def creation_options(&block)
method evaluate_request_options (line 104) | def evaluate_request_options(record)
method evaluate_creation_options (line 114) | def evaluate_creation_options(record)
method extract_options_for (line 123) | def extract_options_for(klass, options)
method options_to_proc (line 131) | def options_to_proc(options)
FILE: lib/action_pack/passkey/request.rb
type ActionPack::Passkey::Request (line 50) | module ActionPack::Passkey::Request
function passkey_creation_params (line 63) | def passkey_creation_params(param: :passkey)
function passkey_request_params (line 68) | def passkey_request_params(param: :passkey)
function passkey_request_options (line 73) | def passkey_request_options(**options)
function passkey_creation_options (line 78) | def passkey_creation_options(**options)
FILE: lib/action_pack/railtie.rb
class ActionPack::Railtie (line 20) | class ActionPack::Railtie < Rails::Railtie
method has_passkeys (line 51) | def self.has_passkeys(**options, &block)
FILE: lib/action_pack/web_authn.rb
type ActionPack::WebAuthn (line 32) | module ActionPack::WebAuthn
class InvalidResponseError (line 33) | class InvalidResponseError < StandardError; end
class InvalidCborError (line 34) | class InvalidCborError < StandardError; end
class InvalidKeyError (line 35) | class InvalidKeyError < StandardError; end
class UnsupportedKeyTypeError (line 36) | class UnsupportedKeyTypeError < StandardError; end
class InvalidOptionsError (line 37) | class InvalidOptionsError < StandardError; end
function relying_party (line 41) | def relying_party
function challenge_verifier (line 46) | def challenge_verifier
function attestation_verifiers (line 52) | def attestation_verifiers
function register_attestation_verifier (line 60) | def register_attestation_verifier(format, verifier)
FILE: lib/action_pack/web_authn/authenticator/assertion_response.rb
class ActionPack::WebAuthn::Authenticator::AssertionResponse (line 33) | class ActionPack::WebAuthn::Authenticator::AssertionResponse < ActionPac...
method initialize (line 40) | def initialize(credential:, authenticator_data:, signature:, **attribu...
method client_data_type_must_be_get (line 51) | def client_data_type_must_be_get
method signature_must_be_valid (line 57) | def signature_must_be_valid
method sign_count_must_increase (line 69) | def sign_count_must_increase
method sign_count_increased? (line 75) | def sign_count_increased?
FILE: lib/action_pack/web_authn/authenticator/attestation.rb
class ActionPack::WebAuthn::Authenticator::Attestation (line 38) | class ActionPack::WebAuthn::Authenticator::Attestation
method wrap (line 46) | def self.wrap(data)
method decode (line 58) | def self.decode(bytes)
method initialize (line 68) | def initialize(authenticator_data:, format:, attestation_statement:)
FILE: lib/action_pack/web_authn/authenticator/attestation_response.rb
class ActionPack::WebAuthn::Authenticator::AttestationResponse (line 30) | class ActionPack::WebAuthn::Authenticator::AttestationResponse < ActionP...
method initialize (line 36) | def initialize(attestation_object:, **attributes)
method attestation (line 43) | def attestation
method authenticator_data (line 48) | def authenticator_data
method client_data_type_must_be_create (line 53) | def client_data_type_must_be_create
method attestation_must_be_valid (line 59) | def attestation_must_be_valid
FILE: lib/action_pack/web_authn/authenticator/attestation_verifiers/none.rb
class ActionPack::WebAuthn::Authenticator::AttestationVerifiers::None (line 17) | class ActionPack::WebAuthn::Authenticator::AttestationVerifiers::None
method verify! (line 18) | def verify!(attestation, client_data_json:)
FILE: lib/action_pack/web_authn/authenticator/data.rb
class ActionPack::WebAuthn::Authenticator::Data (line 45) | class ActionPack::WebAuthn::Authenticator::Data
method wrap (line 65) | def wrap(data)
method decode (line 78) | def decode(bytes)
method initialize (line 135) | def initialize(bytes:, relying_party_id_hash:, flags:, sign_count:, aa...
method user_present? (line 147) | def user_present?
method user_verified? (line 152) | def user_verified?
method backup_eligible? (line 158) | def backup_eligible?
method backed_up? (line 164) | def backed_up?
method public_key (line 171) | def public_key
FILE: lib/action_pack/web_authn/authenticator/response.rb
class ActionPack::WebAuthn::Authenticator::Response (line 33) | class ActionPack::WebAuthn::Authenticator::Response
method initialize (line 48) | def initialize(client_data_json:, challenge: nil, origin: nil, user_ve...
method validate! (line 55) | def validate!
method relying_party (line 62) | def relying_party
method client_data (line 68) | def client_data
method authenticator_data (line 74) | def authenticator_data
method challenge_must_match (line 79) | def challenge_must_match
method challenge_must_not_be_expired (line 89) | def challenge_must_not_be_expired
method origin_must_match (line 101) | def origin_must_match
method must_not_be_cross_origin (line 111) | def must_not_be_cross_origin
method must_not_have_token_binding (line 117) | def must_not_have_token_binding
method relying_party_id_must_match (line 123) | def relying_party_id_must_match
method user_must_be_present (line 132) | def user_must_be_present
method user_must_be_verified_when_required (line 138) | def user_must_be_verified_when_required
FILE: lib/action_pack/web_authn/cbor_decoder.rb
class ActionPack::WebAuthn::CborDecoder (line 53) | class ActionPack::WebAuthn::CborDecoder
method decode (line 95) | def decode(bytes, **args)
method initialize (line 101) | def initialize(bytes, max_depth: MAX_DEPTH, max_size: MAX_SIZE) # :nodoc:
method decode (line 111) | def decode
method major_type (line 133) | def major_type
method peek (line 137) | def peek
method decode_unsigned_integer (line 141) | def decode_unsigned_integer
method decode_negative_integer (line 145) | def decode_negative_integer
method decode_byte_string (line 149) | def decode_byte_string
method decode_text_string (line 157) | def decode_text_string
method decode_array (line 165) | def decode_array
method decode_map (line 173) | def decode_map
method decode_float_or_simple (line 185) | def decode_float_or_simple
method decode_tag (line 198) | def decode_tag
method decode_half_float (line 209) | def decode_half_float
method read_argument (line 227) | def read_argument
method additional_info (line 241) | def additional_info(consume: true)
method indefinite_length? (line 246) | def indefinite_length?
method break_code? (line 250) | def break_code?
method read_bytes (line 254) | def read_bytes(length)
method read_byte (line 262) | def read_byte
FILE: lib/action_pack/web_authn/cose_key.rb
class ActionPack::WebAuthn::CoseKey (line 38) | class ActionPack::WebAuthn::CoseKey
method decode (line 79) | def decode(bytes)
method initialize (line 89) | def initialize(key_type:, algorithm:, parameters:) # :nodoc:
method to_openssl_key (line 103) | def to_openssl_key
method build_ec2_es256_key (line 113) | def build_ec2_es256_key
method build_okp_eddsa_key (line 138) | def build_okp_eddsa_key
method build_rsa_rs256_key (line 157) | def build_rsa_rs256_key
FILE: lib/action_pack/web_authn/current.rb
class ActionPack::WebAuthn::Current (line 21) | class ActionPack::WebAuthn::Current < ActiveSupport::CurrentAttributes
FILE: lib/action_pack/web_authn/public_key_credential.rb
class ActionPack::WebAuthn::PublicKeyCredential (line 57) | class ActionPack::WebAuthn::PublicKeyCredential
method request_options (line 64) | def request_options(**attributes)
method creation_options (line 73) | def creation_options(**attributes)
method register (line 83) | def register(params, challenge: ActionPack::WebAuthn::Current.challeng...
method transform_credentials (line 104) | def transform_credentials(credentials)
method initialize (line 115) | def initialize(id:, public_key:, sign_count:, aaguid: nil, backed_up: ...
method authenticate (line 129) | def authenticate(params, challenge: ActionPack::WebAuthn::Current.chal...
method to_h (line 146) | def to_h
FILE: lib/action_pack/web_authn/public_key_credential/creation_options.rb
class ActionPack::WebAuthn::PublicKeyCredential::CreationOptions (line 42) | class ActionPack::WebAuthn::PublicKeyCredential::CreationOptions < Actio...
method initialize (line 61) | def initialize(attributes = {})
method as_json (line 70) | def as_json(*)
method exclude_credential_json (line 103) | def exclude_credential_json(credential)
FILE: lib/action_pack/web_authn/public_key_credential/options.rb
class ActionPack::WebAuthn::PublicKeyCredential::Options (line 29) | class ActionPack::WebAuthn::PublicKeyCredential::Options
method initialize (line 42) | def initialize(attributes = {})
method validate! (line 48) | def validate!
method inspect (line 55) | def inspect
method challenge (line 69) | def challenge
FILE: lib/action_pack/web_authn/public_key_credential/request_options.rb
class ActionPack::WebAuthn::PublicKeyCredential::RequestOptions (line 26) | class ActionPack::WebAuthn::PublicKeyCredential::RequestOptions < Action...
method initialize (line 30) | def initialize(attributes = {})
method as_json (line 37) | def as_json(*)
method allow_credential_json (line 47) | def allow_credential_json(credential)
FILE: lib/action_pack/web_authn/relying_party.rb
class ActionPack::WebAuthn::RelyingParty (line 29) | class ActionPack::WebAuthn::RelyingParty
method initialize (line 41) | def initialize(id: ActionPack::WebAuthn::Current.host, name: Rails.app...
method as_json (line 47) | def as_json(*)
FILE: lib/auto_link_scrubber.rb
class AutoLinkScrubber (line 5) | class AutoLinkScrubber < Loofah::Scrubber
method initialize (line 23) | def initialize
method scrub (line 27) | def scrub(node)
method autolink_text_node (line 39) | def autolink_text_node(node)
method find_links (line 59) | def find_links(text)
method clean_url (line 83) | def clean_url(url)
FILE: lib/deployment/database_resolver.rb
type Deployment (line 1) | module Deployment
class DatabaseResolver (line 2) | class DatabaseResolver < ActiveRecord::Middleware::DatabaseSelector::R...
method in_primary_datacenter? (line 3) | def self.in_primary_datacenter?
method reading_request? (line 7) | def reading_request?(request)
method read_from_primary? (line 13) | def read_from_primary?
FILE: lib/fizzy.rb
type Fizzy (line 1) | module Fizzy
function saas? (line 3) | def saas?
function db_adapter (line 8) | def db_adapter
function configure_bundle (line 12) | def configure_bundle
class DbAdapter (line 19) | class DbAdapter
method initialize (line 20) | def initialize(name)
method to_s (line 24) | def to_s
method sqlite? (line 29) | def sqlite?
FILE: lib/rails_ext/action_pack_passkey_infer_name_from_aaguid.rb
type ActionPackPasskeyInferNameFromAaguid (line 1) | module ActionPackPasskeyInferNameFromAaguid
function register (line 5) | def register(...)
function authenticator (line 12) | def authenticator
FILE: lib/rails_ext/active_record_date_arithmetic.rb
type MysqlDateArithmetic (line 12) | module MysqlDateArithmetic
function date_subtract (line 22) | def date_subtract(date_column, seconds_expression)
type SqliteDateArithmetic (line 28) | module SqliteDateArithmetic
function date_subtract (line 38) | def date_subtract(date_column, seconds_expression)
FILE: lib/rails_ext/active_record_replica_support.rb
type ActiveRecordReplicaSupport (line 10) | module ActiveRecordReplicaSupport
function configure_replica_connections (line 22) | def configure_replica_connections
function replica_configured? (line 34) | def replica_configured?
function with_reading_role (line 42) | def with_reading_role(&block)
FILE: lib/rails_ext/active_record_uuid_type.rb
type ActiveRecord (line 2) | module ActiveRecord
type Type (line 3) | module Type
class Uuid (line 4) | class Uuid < Binary
method generate (line 8) | def generate
method hex_to_base36 (line 14) | def hex_to_base36(hex)
method base36_to_hex (line 18) | def base36_to_hex(base36)
method serialize (line 23) | def serialize(value)
method deserialize (line 30) | def deserialize(value)
method cast (line 37) | def cast(value)
FILE: lib/rails_ext/active_storage_analyze_job_skip_detached.rb
type ActiveStorageAnalyzeJobSkipDetached (line 4) | module ActiveStorageAnalyzeJobSkipDetached
function perform (line 5) | def perform(blob)
FILE: lib/rails_ext/active_storage_analyze_job_suppress_broadcasts.rb
type ActiveStorageAnalyzeJobSuppressBroadcasts (line 5) | module ActiveStorageAnalyzeJobSuppressBroadcasts
function perform (line 6) | def perform(blob)
FILE: lib/rails_ext/active_storage_authorization.rb
function accessible_to? (line 2) | def accessible_to?(user)
function publicly_accessible? (line 6) | def publicly_accessible?
function accessible_to? (line 12) | def accessible_to?(user)
function publicly_accessible? (line 16) | def publicly_accessible?
type ActiveStorage::Authorize (line 22) | module ActiveStorage::Authorize
function publicly_accessible_blob? (line 34) | def publicly_accessible_blob?
function ensure_accessible (line 38) | def ensure_accessible
FILE: lib/rails_ext/active_storage_blob_service_url_for_direct_upload_expiry.rb
type ActiveStorage (line 4) | module ActiveStorage
type ActiveStorageBlobServiceUrlForDirectUploadExpiry (line 8) | module ActiveStorageBlobServiceUrlForDirectUploadExpiry
function service_url_for_direct_upload (line 16) | def service_url_for_direct_upload(expires_in: ActiveStorage.service_ur...
FILE: lib/rails_ext/active_support_array_conversions.rb
type ChoiceSentenceArrayConversion (line 1) | module ChoiceSentenceArrayConversion
function to_choice_sentence (line 2) | def to_choice_sentence
FILE: lib/rails_ext/prepend_order.rb
type ActiveRecordRelationPrependOrder (line 1) | module ActiveRecordRelationPrependOrder
function prepend_order (line 5) | def prepend_order(*args)
FILE: lib/rails_ext/string.rb
class String (line 1) | class String
method all_emoji? (line 2) | def all_emoji?
FILE: lib/web_push/notification.rb
class WebPush::Notification (line 1) | class WebPush::Notification
method initialize (line 2) | def initialize(title:, body:, url:, badge:, endpoint:, endpoint_ip:, p...
method deliver (line 7) | def deliver(connection: nil)
method vapid_identification (line 17) | def vapid_identification
method encoded_message (line 22) | def encoded_message
method icon_path (line 26) | def icon_path
FILE: lib/web_push/pool.rb
class WebPush::Pool (line 2) | class WebPush::Pool
method initialize (line 5) | def initialize(invalid_subscription_handler:)
method queue (line 12) | def queue(payload, subscriptions)
method shutdown (line 18) | def shutdown
method deliver_later (line 25) | def deliver_later(payload, subscription)
method deliver (line 38) | def deliver(notification, id)
method invalidate_subscription_later (line 44) | def invalidate_subscription_later(id)
method shutdown_pool (line 52) | def shutdown_pool(pool)
FILE: saas/app/controllers/admin/audits_controller.rb
class Admin::AuditsController (line 1) | class Admin::AuditsController < ::AdminController
method require_authentication (line 4) | def require_authentication
method authenticate_by_audit_bearer_token (line 8) | def authenticate_by_audit_bearer_token
method find_current_auditor (line 14) | def find_current_auditor
FILE: saas/app/controllers/admin/stats_controller.rb
class Admin::StatsController (line 1) | class Admin::StatsController < AdminController
method show (line 4) | def show
FILE: saas/app/controllers/concerns/card/storage_limited.rb
type Card::StorageLimited (line 1) | module Card::StorageLimited
function ensure_within_storage_limit (line 5) | def ensure_within_storage_limit
FILE: saas/app/controllers/concerns/card/storage_limited/commenting.rb
type Card::StorageLimited::Commenting (line 1) | module Card::StorageLimited::Commenting
FILE: saas/app/controllers/concerns/card/storage_limited/creation.rb
type Card::StorageLimited::Creation (line 1) | module Card::StorageLimited::Creation
FILE: saas/app/controllers/concerns/card/storage_limited/publishing.rb
type Card::StorageLimited::Publishing (line 1) | module Card::StorageLimited::Publishing
FILE: saas/app/controllers/my/devices_controller.rb
class My::DevicesController (line 1) | class My::DevicesController < ApplicationController
method index (line 7) | def index
method create (line 11) | def create
method destroy (line 16) | def destroy
method set_device (line 25) | def set_device
method device_params (line 29) | def device_params
FILE: saas/app/jobs/application_push_notification_job.rb
class ApplicationPushNotificationJob (line 1) | class ApplicationPushNotificationJob < ActionPushNative::NotificationJob
FILE: saas/app/models/account/storage_exception.rb
class Account::StorageException (line 1) | class Account::StorageException < SaasRecord
FILE: saas/app/models/account/storage_limited.rb
type Account::StorageLimited (line 1) | module Account::StorageLimited
function storage_limit (line 11) | def storage_limit
function exceeding_storage_limit? (line 15) | def exceeding_storage_limit?
function nearing_storage_limit? (line 19) | def nearing_storage_limit?
function add_storage_exception (line 23) | def add_storage_exception(bytes)
FILE: saas/app/models/application_push_device.rb
class ApplicationPushDevice (line 1) | class ApplicationPushDevice < ActionPushNative::Device
method register (line 4) | def self.register(session:, token:, platform:, name: nil)
FILE: saas/app/models/application_push_notification.rb
class ApplicationPushNotification (line 1) | class ApplicationPushNotification < ActionPushNative::Notification
FILE: saas/app/models/identity/devices.rb
type Identity::Devices (line 1) | module Identity::Devices
FILE: saas/app/models/notification/push_target/native.rb
class Notification::PushTarget::Native (line 1) | class Notification::PushTarget::Native < Notification::PushTarget
method process (line 2) | def process
method devices (line 9) | def devices
method payload (line 13) | def payload
method native_notification (line 17) | def native_notification
method interruption_level (line 56) | def interruption_level
FILE: saas/app/models/saas_record.rb
class SaasRecord (line 1) | class SaasRecord < ActiveRecord::Base
FILE: saas/app/models/session/devices.rb
type Session::Devices (line 1) | module Session::Devices
FILE: saas/app/models/subscription.rb
class Subscription (line 1) | class Subscription < Queenbee::Subscription
method short_name (line 4) | def self.short_name
class FreeV1 (line 8) | class FreeV1 < Subscription
FILE: saas/db/migrate/20251202200249_create_console1984_tables.console1984.rb
class CreateConsole1984Tables (line 2) | class CreateConsole1984Tables < ActiveRecord::Migration[7.0]
method change (line 3) | def change
FILE: saas/db/migrate/20251202205753_create_auditing_tables.audits1984.rb
class CreateAuditingTables (line 2) | class CreateAuditingTables < ActiveRecord::Migration[7.0]
method change (line 3) | def change
FILE: saas/db/migrate/20251203144630_create_account_subscriptions.rb
class CreateAccountSubscriptions (line 1) | class CreateAccountSubscriptions < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: saas/db/migrate/20251215140000_create_account_overridden_limits.rb
class CreateAccountOverriddenLimits (line 1) | class CreateAccountOverriddenLimits < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: saas/db/migrate/20251215160000_create_account_billing_waivers.rb
class CreateAccountBillingWaivers (line 1) | class CreateAccountBillingWaivers < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: saas/db/migrate/20251215170000_add_next_amount_due_in_cents_to_account_subscriptions.rb
class AddNextAmountDueInCentsToAccountSubscriptions (line 1) | class AddNextAmountDueInCentsToAccountSubscriptions < ActiveRecord::Migr...
method change (line 2) | def change
FILE: saas/db/migrate/20251216000000_add_bytes_used_to_account_overridden_limits.rb
class AddBytesUsedToAccountOverriddenLimits (line 1) | class AddBytesUsedToAccountOverriddenLimits < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: saas/db/migrate/20260114203313_create_action_push_native_devices.rb
class CreateActionPushNativeDevices (line 1) | class CreateActionPushNativeDevices < ActiveRecord::Migration[8.0]
method change (line 2) | def change
FILE: saas/db/migrate/20260126230838_create_auditor_tokens.audits1984.rb
class CreateAuditorTokens (line 2) | class CreateAuditorTokens < ActiveRecord::Migration[7.0]
method change (line 3) | def change
FILE: saas/db/migrate/20260317000000_drop_billing_tables.rb
class DropBillingTables (line 1) | class DropBillingTables < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: saas/db/migrate/20260319142914_create_account_storage_exceptions.rb
class CreateAccountStorageExceptions (line 1) | class CreateAccountStorageExceptions < ActiveRecord::Migration[8.2]
method change (line 2) | def change
FILE: saas/lib/fizzy/saas.rb
type Fizzy (line 4) | module Fizzy
type Saas (line 5) | module Saas
function append_test_paths (line 6) | def self.append_test_paths
FILE: saas/lib/fizzy/saas/authorization.rb
type Fizzy (line 1) | module Fizzy
type Saas (line 2) | module Saas
type Authorization (line 3) | module Authorization
type Controller (line 4) | module Controller
function ensure_only_employees_can_access_non_production_remote_environments (line 12) | def ensure_only_employees_can_access_non_production_remote_envir...
type Identity (line 17) | module Identity
function employee? (line 22) | def employee?
FILE: saas/lib/fizzy/saas/engine.rb
type Fizzy (line 8) | module Fizzy
type Saas (line 9) | module Saas
class Engine (line 10) | class Engine < ::Rails::Engine
FILE: saas/lib/fizzy/saas/gvl_instrumentation.rb
type Fizzy (line 1) | module Fizzy
type Saas (line 2) | module Saas
class GvlInstrumentation (line 3) | class GvlInstrumentation
method initialize (line 4) | def initialize(app)
method call (line 8) | def call(env)
FILE: saas/lib/fizzy/saas/signup.rb
type Fizzy (line 1) | module Fizzy
type Saas (line 2) | module Saas
type Signup (line 3) | module Signup
function create_tenant (line 11) | def create_tenant
function handle_account_creation_error (line 16) | def handle_account_creation_error(error)
function queenbee_account_attributes (line 20) | def queenbee_account_attributes
FILE: saas/lib/fizzy/saas/testing.rb
function next_id (line 6) | def next_id
type Fizzy::Saas::EngineFixtures (line 12) | module Fizzy::Saas::EngineFixtures
function included (line 13) | def included(base)
FILE: saas/lib/fizzy/saas/transaction_pinning.rb
type TransactionPinning (line 1) | module TransactionPinning
class Middleware (line 2) | class Middleware
method initialize (line 6) | def initialize(app)
method call (line 11) | def call(env)
method wait_for_replica_catchup (line 30) | def wait_for_replica_catchup(request, replica_metrics)
method capture_transaction_id (line 43) | def capture_transaction_id(request)
method replica_has_transaction (line 47) | def replica_has_transaction(txn)
method tracking_replica_wait_time (line 55) | def tracking_replica_wait_time(replica_metrics)
FILE: saas/lib/fizzy/saas/true_client_ip.rb
class TrackTrueClientIp (line 10) | class TrackTrueClientIp
method initialize (line 11) | def initialize(app)
method call (line 15) | def call(env)
FILE: saas/lib/fizzy/saas/version.rb
type Fizzy (line 1) | module Fizzy
type Saas (line 2) | module Saas
FILE: saas/lib/rails_ext/active_record_tasks_database_tasks.rb
type ActiveRecordTasksDatabaseTasksExtension (line 1) | module ActiveRecordTasksDatabaseTasksExtension
function schema_dump_path (line 6) | def schema_dump_path(db_config, format = db_config.schema_format)
FILE: saas/lib/yabeda/gvl.rb
type Yabeda (line 1) | module Yabeda
type GVL (line 2) | module GVL
function install! (line 5) | def self.install!
FILE: saas/lib/yabeda/solid_queue.rb
type Yabeda (line 1) | module Yabeda
type SolidQueue (line 2) | module SolidQueue
function install! (line 3) | def self.install!
FILE: saas/test/controllers/admin/audits_controller_test.rb
class Admin::AuditsControllerTest (line 3) | class Admin::AuditsControllerTest < ActionDispatch::IntegrationTest
FILE: saas/test/controllers/admin/stats_controller_test.rb
class Admin::StatsControllerTest (line 3) | class Admin::StatsControllerTest < ActionDispatch::IntegrationTest
FILE: saas/test/controllers/card/storage_limited/commenting_test.rb
class Card::StorageLimited::CommentingTest (line 3) | class Card::StorageLimited::CommentingTest < ActionDispatch::Integration...
FILE: saas/test/controllers/card/storage_limited/creation_test.rb
class Card::StorageLimited::CreationTest (line 3) | class Card::StorageLimited::CreationTest < ActionDispatch::IntegrationTest
FILE: saas/test/controllers/card/storage_limited/publishing_test.rb
class Card::StorageLimited::PublishingTest (line 3) | class Card::StorageLimited::PublishingTest < ActionDispatch::Integration...
FILE: saas/test/controllers/card/storage_limited_test.rb
class Card::StorageLimitedTest (line 3) | class Card::StorageLimitedTest < ActionDispatch::IntegrationTest
FILE: saas/test/controllers/comment/storage_limited_test.rb
class Comment::StorageLimitedTest (line 3) | class Comment::StorageLimitedTest < ActionDispatch::IntegrationTest
FILE: saas/test/controllers/my/devices_controller_test.rb
class My::DevicesControllerTest (line 3) | class My::DevicesControllerTest < ActionDispatch::IntegrationTest
FILE: saas/test/controllers/non_production_remote_access_test.rb
class NonProductionRemoteAccessTest (line 3) | class NonProductionRemoteAccessTest < ActionDispatch::IntegrationTest
FILE: saas/test/lib/true_client_ip_test.rb
class TrackTrueClientIpTest (line 3) | class TrackTrueClientIpTest < ActiveSupport::TestCase
FILE: saas/test/models/account/storage_exception_test.rb
class Account::StorageExceptionTest (line 3) | class Account::StorageExceptionTest < ActiveSupport::TestCase
FILE: saas/test/models/account/storage_limited_test.rb
class Account::StorageLimitedTest (line 3) | class Account::StorageLimitedTest < ActiveSupport::TestCase
FILE: saas/test/models/identity_test.rb
class Fizzy::Saas::IdentityTest (line 3) | class Fizzy::Saas::IdentityTest < ActiveSupport::TestCase
FILE: saas/test/models/notification/push_target/native_test.rb
class Notification::PushTarget::NativeTest (line 3) | class Notification::PushTarget::NativeTest < ActiveSupport::TestCase
method assert_native_push_delivery (line 207) | def assert_native_push_delivery(count: 1, &block)
method assert_no_native_push_delivery (line 213) | def assert_no_native_push_delivery(&block)
method stub_push_services (line 219) | def stub_push_services
FILE: saas/test/models/session/devices_test.rb
class Session::DevicesTest (line 3) | class Session::DevicesTest < ActiveSupport::TestCase
FILE: saas/test/models/signup_test.rb
class Fizzy::Saas::SignupTest (line 3) | class Fizzy::Saas::SignupTest < ActiveSupport::TestCase
FILE: script/fix-active-storage-links.rb
class FixActiveStorage (line 9) | class FixActiveStorage
method initialize (line 12) | def initialize(scope = nil)
method ingest_blob_keys (line 25) | def ingest_blob_keys(db_path)
method ingest_untenanted (line 33) | def ingest_untenanted(untenanted_db_path)
method perform (line 40) | def perform
method fix_avatars (line 49) | def fix_avatars
method fix_mentions (line 103) | def fix_mentions
method fix_attachments (line 133) | def fix_attachments
class Models (line 188) | class Models
method initialize (line 191) | def initialize(db_path)
method accounts (line 213) | def accounts
method blobs (line 219) | def blobs
method attachments (line 230) | def attachments
method users (line 241) | def users
me
Condensed preview — 1525 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,362K chars).
[
{
"path": ".claude/CLAUDE.md",
"chars": 14,
"preview": "@../AGENTS.md\n"
},
{
"path": ".dockerignore",
"chars": 794,
"preview": "# See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.\n\n# Ignore git d"
},
{
"path": ".gitattributes",
"chars": 246,
"preview": "# See https://git-scm.com/docs/gitattributes for more about git attribute files.\n\n# Mark the database schema as having b"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 225,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Features, Bug Reports, Questions\n url: https://github.com/baseca"
},
{
"path": ".github/ISSUE_TEMPLATE/preapproved.md",
"chars": 241,
"preview": "---\nname: Pre-Discussed and Approved Topics\nabout: |-\n For topics already discussed and approved in the GitHub Discussi"
},
{
"path": ".github/dependabot.yml",
"chars": 887,
"preview": "version: 2\n\nregistries:\n github-basecamp:\n type: git\n url: https://github.com\n username: x-access-token\n pa"
},
{
"path": ".github/workflows/ci-checks.yml",
"chars": 1939,
"preview": "name: Checks\n\non:\n pull_request:\n\npermissions:\n contents: read\n\njobs:\n gemfile-drift:\n name: Gemfile drift\n run"
},
{
"path": ".github/workflows/ci-oss.yml",
"chars": 257,
"preview": "name: CI (OSS)\n\non:\n pull_request:\n types: [opened, synchronize]\n\npermissions:\n contents: read\n\njobs:\n test:\n i"
},
{
"path": ".github/workflows/ci-saas.yml",
"chars": 333,
"preview": "name: CI (SaaS)\n\non:\n push:\n\npermissions:\n contents: read\n\njobs:\n test_oss:\n name: Test (OSS)\n uses: ./.github/"
},
{
"path": ".github/workflows/dependabot-sync-saas-lockfile.yml",
"chars": 947,
"preview": "name: Sync Gemfile.saas.lock\n\non:\n push:\n branches:\n - \"dependabot/bundler/**\"\n paths:\n - Gemfile.lock\n"
},
{
"path": ".github/workflows/publish-image.yml",
"chars": 7202,
"preview": "name: Build and publish container image to GHCR\n\non:\n push:\n branches:\n - main\n tags:\n - 'v*'\n workflo"
},
{
"path": ".github/workflows/test.yml",
"chars": 1871,
"preview": "name: Test\n\non:\n workflow_call:\n inputs:\n saas:\n type: boolean\n required: true\n secrets:\n "
},
{
"path": ".gitignore",
"chars": 887,
"preview": "# See https://help.github.com/articles/ignoring-files for more about ignoring files.\n#\n# If you find yourself ignoring t"
},
{
"path": ".gitleaks.toml",
"chars": 332,
"preview": "[extend]\nuseDefault = true\n\n[allowlist]\npaths = [\n '''log''',\n '''tmp''',\n '''.*\\.yml\\.enc''',\n '''docs/''',\n '''te"
},
{
"path": ".gitleaksignore",
"chars": 178,
"preview": "d8463077:gems/fizzy-saas/bin/setup:generic-api-key:54\nc4073c1c:app/models/integration/basecamp.rb:generic-api-key:3\nc407"
},
{
"path": ".mise.toml",
"chars": 123,
"preview": "[settings]\nidiomatic_version_file_enable_tools = [\"ruby\"]\n\n[env]\nPROMETHEUS_EXPORTER_URL = \"http://127.0.0.1:9306/metric"
},
{
"path": ".rubocop.yml",
"chars": 376,
"preview": "# Omakase Ruby styling for Rails\ninherit_gem: { rubocop-rails-omakase: rubocop.yml }\n\n# Overwrite or add rules to create"
},
{
"path": ".ruby-version",
"chars": 6,
"preview": "3.4.7\n"
},
{
"path": "AGENTS.md",
"chars": 5767,
"preview": "# Fizzy\n\nThis file provides guidance to AI coding agents working with this repository.\n\n## What is Fizzy?\n\nFizzy is a co"
},
{
"path": "CONTRIBUTING.md",
"chars": 1899,
"preview": "# How to contribute to Fizzy\n\nFizzy uses GitHub\n[discussions](https://github.com/basecamp/fizzy/discussions) to track\nfe"
},
{
"path": "Dockerfile",
"chars": 3024,
"preview": "# syntax=docker/dockerfile:1\n# check=error=true\n\n# This Dockerfile is designed for production, not development. Use with"
},
{
"path": "Gemfile",
"chars": 1485,
"preview": "source \"https://rubygems.org\"\n\ngit_source(:bc) { |repo| \"https://github.com/basecamp/#{repo}\" }\n\ngem \"rails\", github: \"r"
},
{
"path": "Gemfile.saas",
"chars": 1012,
"preview": "# This Gemfile extends the base Gemfile with SaaS-specific dependencies\neval_gemfile \"Gemfile\"\n\ngit_source(:bc) { |repo|"
},
{
"path": "LICENSE.md",
"chars": 1438,
"preview": "# O'Saasy License Agreement\n\nCopyright © 2025, 37signals LLC.\n\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "README.md",
"chars": 1228,
"preview": "# Fizzy\n\nThis is the source code of [Fizzy](https://fizzy.do/), the Kanban tracking tool for issues and ideas by [37sign"
},
{
"path": "Rakefile",
"chars": 227,
"preview": "# Add your own tasks in files placed in lib/tasks ending in .rake,\n# for example lib/tasks/capistrano.rake, and they wil"
},
{
"path": "STYLE.md",
"chars": 5070,
"preview": "\n# Style\n\nWe aim to write code that is a pleasure to read, and we have a lot of opinions about how to do it well. Writin"
},
{
"path": "app/assets/images/.keep",
"chars": 0,
"preview": ""
},
{
"path": "app/assets/stylesheets/_global.css",
"chars": 16867,
"preview": "@layer reset, base, components, modules, utilities, native, platform;\n\n:root {\n /* Insets - The mobile apps may inject "
},
{
"path": "app/assets/stylesheets/access-tokens.css",
"chars": 585,
"preview": ".access-tokens {\n border-collapse: collapse;\n font-size: var(--text-small);\n inline-size: 100%;\n margin-block-end: 2"
},
{
"path": "app/assets/stylesheets/android.css",
"chars": 252,
"preview": "@layer platform {\n [data-platform~=android] {\n .hide-on-android {\n display: none;\n }\n\n /* Filters\n /* "
},
{
"path": "app/assets/stylesheets/animation.css",
"chars": 2889,
"preview": "@layer utilities {\n .shake {\n animation: shake 400ms both;\n }\n\n @keyframes appear-then-fade {\n 0%,100% { opacit"
},
{
"path": "app/assets/stylesheets/attachments.css",
"chars": 4511,
"preview": "@layer components {\n .attachment {\n block-size: auto;\n display: block;\n inline-size: fit-content;\n max-inli"
},
{
"path": "app/assets/stylesheets/autoresize.css",
"chars": 596,
"preview": "@layer components {\n @supports not (field-sizing: content) {\n .autoresize__wrapper {\n display: grid !important;"
},
{
"path": "app/assets/stylesheets/avatars.css",
"chars": 717,
"preview": "@layer components {\n .avatar {\n --avatar-border-radius: 50%;\n --avatar-size-default: 5ch;\n --btn-border-size: "
},
{
"path": "app/assets/stylesheets/bar.css",
"chars": 2118,
"preview": "@layer components {\n .bar {\n --row-gap: 0.2lh;\n\n background-color: var(--color-terminal-bg);\n block-size: calc"
},
{
"path": "app/assets/stylesheets/base.css",
"chars": 2682,
"preview": "@layer base {\n html {\n font-size: 100%;\n\n @media (min-width: 100ch) {\n font-size: 1.1875rem;\n }\n }\n\n bo"
},
{
"path": "app/assets/stylesheets/blank-slates.css",
"chars": 628,
"preview": "/* Styles for the blank slate. To manage when they are shown/hidden, do so in context */\n\n@layer components {\n .blank-s"
},
{
"path": "app/assets/stylesheets/bubble.css",
"chars": 1464,
"preview": "@layer components {\n .bubble {\n --bubble-color: var(--card-color, oklch(var(--lch-blue-medium)));\n --bubble-numbe"
},
{
"path": "app/assets/stylesheets/buttons.css",
"chars": 6947,
"preview": "@layer components {\n .btn {\n --icon-size: var(--btn-icon-size, 1.3em);\n --btn-border-radius: 99rem;\n --btn-hov"
},
{
"path": "app/assets/stylesheets/card-columns.css",
"chars": 20766,
"preview": "@layer components {\n /* Layout adjustments for contained scrolling\n /* -----------------------------------------------"
},
{
"path": "app/assets/stylesheets/card-perma.css",
"chars": 13406,
"preview": "/* Card container for the perma. Tools and actions and whatnot */\n\n@layer components {\n .card-perma {\n --actions-blo"
},
{
"path": "app/assets/stylesheets/cards.css",
"chars": 13360,
"preview": "@layer components {\n /* Base\n /* ------------------------------------------------------------------------ */\n\n .card "
},
{
"path": "app/assets/stylesheets/circled-text.css",
"chars": 1010,
"preview": "@layer components {\n .circled-text {\n --circled-color: oklch(var(--lch-blue-dark));\n --circled-padding: -0.5ch;\n\n"
},
{
"path": "app/assets/stylesheets/color-picker.css",
"chars": 277,
"preview": "@layer components {\n .color-picker__colors {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: var"
},
{
"path": "app/assets/stylesheets/comments.css",
"chars": 5404,
"preview": "@layer components {\n .comments {\n --avatar-size: 2.33em;\n --comment-padding-block: var(--block-space-half);\n -"
},
{
"path": "app/assets/stylesheets/credentials.css",
"chars": 858,
"preview": "@layer components {\n .credential {\n border-block-start: var(--border);\n list-style: none;\n\n &:last-child {\n "
},
{
"path": "app/assets/stylesheets/dialog.css",
"chars": 1299,
"preview": "@layer components {\n /* Prevent page scrolling when modal dialog is open */\n html:has(dialog:modal) {\n overflow: hi"
},
{
"path": "app/assets/stylesheets/dividers.css",
"chars": 525,
"preview": "@layer components {\n .divider {\n --divider-color: var(--color-ink-light);\n\n align-items: center;\n display: fle"
},
{
"path": "app/assets/stylesheets/drag_and_drop.css",
"chars": 511,
"preview": "@layer components {\n .drag-and-drop__dragged-item {\n box-shadow: none;\n filter: grayscale(1) brightness(0.97);\n "
},
{
"path": "app/assets/stylesheets/events.css",
"chars": 9168,
"preview": "@layer components {\n /* Events header\n /* ------------------------------------------------------------------------ */\n"
},
{
"path": "app/assets/stylesheets/expandable.css",
"chars": 210,
"preview": "@layer components {\n .expandable-on-native {\n body:not([data-platform~=native]) & {\n &::details-content {\n "
},
{
"path": "app/assets/stylesheets/filters.css",
"chars": 3965,
"preview": "@layer components {\n #header:has(.filters) {\n position: relative;\n }\n\n .filters {\n align-items: center;\n dis"
},
{
"path": "app/assets/stylesheets/flash.css",
"chars": 662,
"preview": "@layer components {\n .flash {\n display: flex;\n inset-block-start: calc(var(--block-space) + var(--custom-safe-ins"
},
{
"path": "app/assets/stylesheets/font-face.css",
"chars": 1125,
"preview": "@layer base {\n /*\n Segoe UI Variable Fizzy font face configuration.\n\n 1. Segoe UI Variable (Weights 100-700):\n "
},
{
"path": "app/assets/stylesheets/golden-effect.css",
"chars": 919,
"preview": "@layer components {\n .golden-effect {\n /* Uncomment below to use the card color for golden effect */\n /* --color-"
},
{
"path": "app/assets/stylesheets/header.css",
"chars": 3441,
"preview": "@layer components {\n /* Centered title with space for two buttons on either side */\n .header {\n --header-gap: 0.5ch"
},
{
"path": "app/assets/stylesheets/icons.css",
"chars": 5076,
"preview": "@layer components {\n .icon {\n -webkit-touch-callout: none;\n background-color: currentColor;\n block-size: var(-"
},
{
"path": "app/assets/stylesheets/import.css",
"chars": 793,
"preview": "@layer components {\n .import-status {\n --import-status-border-color: var(--color-ink-light);\n --import-status-col"
},
{
"path": "app/assets/stylesheets/inputs.css",
"chars": 7565,
"preview": "@layer components {\n /* Text inputs */\n .input {\n accent-color: var(--input-accent-color, var(--color-ink));\n ba"
},
{
"path": "app/assets/stylesheets/ios.css",
"chars": 1147,
"preview": "@layer platform {\n :root:has([data-platform~=ios]) {\n &[data-text-size=xsmall] { font-size: 14px; }\n &[data-text-"
},
{
"path": "app/assets/stylesheets/knobs.css",
"chars": 4027,
"preview": "@layer components {\n .knob {\n --knob-angle-reserve: 120deg;\n --knob-option-angle: calc((360deg - var(--knob-angle"
},
{
"path": "app/assets/stylesheets/layout.css",
"chars": 916,
"preview": "@layer base {\n body {\n display: grid;\n grid-template-rows: auto 1fr auto 9em;\n\n &.public {\n grid-template"
},
{
"path": "app/assets/stylesheets/lexxy.css",
"chars": 5689,
"preview": "@import url(\"lexxy-variables.css\") layer(base);\n@import url(\"lexxy-content.css\") layer(base);\n@import url(\"lexxy-editor."
},
{
"path": "app/assets/stylesheets/lightbox.css",
"chars": 2136,
"preview": "@layer components {\n .lightbox {\n --dialog-duration: 350ms;\n --lightbox-padding: 3vmin;\n\n background-color: tr"
},
{
"path": "app/assets/stylesheets/markdown.css",
"chars": 766,
"preview": "@layer components {\n .heading__link {\n --opacity: 0.5;\n --size: 0.8em;\n\n background: url(link.svg) no-repeat c"
},
{
"path": "app/assets/stylesheets/native.css",
"chars": 2911,
"preview": "@layer native {\n [data-platform~=native] {\n --footer-height: 0;\n\n -webkit-tap-highlight-color: transparent;\n\n "
},
{
"path": "app/assets/stylesheets/nav.css",
"chars": 6072,
"preview": "@layer components {\n /* Trigger\n /* ------------------------------------------------------------------------ */\n\n .na"
},
{
"path": "app/assets/stylesheets/notifications.css",
"chars": 2149,
"preview": "@layer components {\n /* Notifications list\n /* -----------------------------------------------------------------------"
},
{
"path": "app/assets/stylesheets/pagination.css",
"chars": 591,
"preview": "@layer components {\n .pagination-link {\n display: block;\n flex: 1;\n min-block-size: 1.5rem;\n pointer-events"
},
{
"path": "app/assets/stylesheets/panels.css",
"chars": 820,
"preview": "@layer components {\n .panel {\n background-color: var(--panel-bg, var(--color-canvas));\n border: var(--panel-borde"
},
{
"path": "app/assets/stylesheets/performance-notice.css",
"chars": 291,
"preview": ".performance-notice {\n background: oklch(var(--lch-yellow-lightest));\n border-radius: 1ch;\n border: 1px solid oklch(v"
},
{
"path": "app/assets/stylesheets/pins.css",
"chars": 65,
"preview": "@layer components {\n .pins-list {\n --panel-size: 45ch;\n }\n}\n"
},
{
"path": "app/assets/stylesheets/popup.css",
"chars": 5191,
"preview": "@layer components {\n .popup {\n --btn-background: transparent;\n --panel-border-radius: 0.5em;\n --panel-padding:"
},
{
"path": "app/assets/stylesheets/print.css",
"chars": 5138,
"preview": "@media print {\n /* Global\n /* ------------------------------------------------------------------------ */\n\n :root {\n "
},
{
"path": "app/assets/stylesheets/pwa.css",
"chars": 188,
"preview": "/* PWA install */\n.pwa__instructions {\n @media (display-mode: standalone) {\n display: none;\n }\n}\n\n.pwa__installer {"
},
{
"path": "app/assets/stylesheets/qr-codes.css",
"chars": 165,
"preview": "@layer components {\n .qr-code {\n aspect-ratio: 1;\n border-radius: 1ch;\n inline-size: clamp(20ch, 50dvh, 70ch);"
},
{
"path": "app/assets/stylesheets/reactions.css",
"chars": 4622,
"preview": "@layer components {\n .reactions {\n --btn-icon-size: 1.3em;\n --column-gap: 0.4ch;\n --reaction-border-color: var"
},
{
"path": "app/assets/stylesheets/reset.css",
"chars": 1679,
"preview": "@layer reset {\n /*\n * Modern CSS Reset\n * @link https://github.com/hankchizljaw/modern-css-reset\n */\n\n /* Box sizin"
},
{
"path": "app/assets/stylesheets/search.css",
"chars": 2482,
"preview": "summary {\n\n &::-webkit-details-marker {\n display: none !important;\n }\n\n &::marker {\n display: none !important;\n"
},
{
"path": "app/assets/stylesheets/separators.css",
"chars": 494,
"preview": "@layer components {\n .separator {\n block-size: 100%;\n border-block: 0;\n border-inline-end: 0;\n border-inlin"
},
{
"path": "app/assets/stylesheets/settings.css",
"chars": 2887,
"preview": "@layer components {\n .settings {\n --settings-spacer: var(--block-space);\n --settings-item-padding-inline: 0.5ch;\n"
},
{
"path": "app/assets/stylesheets/spinners.css",
"chars": 590,
"preview": "@layer components {\n .spinner {\n position: relative;\n\n &::before {\n --mask: no-repeat radial-gradient(#000 6"
},
{
"path": "app/assets/stylesheets/steps.css",
"chars": 2218,
"preview": "@layer components {\n .step {\n display: grid;\n grid-template-columns: 1em auto auto;\n gap: calc(var(--inline-sp"
},
{
"path": "app/assets/stylesheets/syntax.css",
"chars": 3205,
"preview": "@layer components {\n .highlight {\n /* Named color values */\n --keyword: lch(50.16 68.78 25.97);\n --entity: lch"
},
{
"path": "app/assets/stylesheets/theme-switcher.css",
"chars": 654,
"preview": "@layer components {\n .theme-switcher {\n @media (max-width: 479px) {\n --row-gap: 1ch;\n\n flex-direction: col"
},
{
"path": "app/assets/stylesheets/toggles.css",
"chars": 223,
"preview": "@layer components {\n .toggler--toggled {\n .toggler__visible-when-off {\n display: none;\n }\n\n .toggler__vis"
},
{
"path": "app/assets/stylesheets/tooltips.css",
"chars": 1354,
"preview": "@layer components {\n [data-controller~=\"tooltip\"] {\n --tooltip-delay: 750ms;\n --tooltip-duration: 150ms;\n\n .fo"
},
{
"path": "app/assets/stylesheets/trays.css",
"chars": 13615,
"preview": "@layer components {\n /* Container\n /* ------------------------------------------------------------------------ */\n\n ."
},
{
"path": "app/assets/stylesheets/user.css",
"chars": 96,
"preview": "@layer components {\n .user-edit-link {\n inset: 0 0 auto auto;\n position: absolute;\n }\n}\n"
},
{
"path": "app/assets/stylesheets/utilities.css",
"chars": 9724,
"preview": "@layer utilities {\n /* Text */\n .txt-xx-small { font-size: var(--text-xx-small); }\n .txt-x-small { font-size: var(--t"
},
{
"path": "app/assets/stylesheets/welcome-letter.css",
"chars": 565,
"preview": "@layer components {\n .welcome-letter {\n position: relative;\n view-transition-name: welcome-letter;\n z-index: v"
},
{
"path": "app/channels/application_cable/connection.rb",
"chars": 635,
"preview": "module ApplicationCable\n class Connection < ActionCable::Connection::Base\n identified_by :current_user\n\n def conn"
},
{
"path": "app/controllers/account/cancellations_controller.rb",
"chars": 312,
"preview": "class Account::CancellationsController < ApplicationController\n before_action :ensure_owner\n\n def create\n Current.a"
},
{
"path": "app/controllers/account/entropies_controller.rb",
"chars": 620,
"preview": "class Account::EntropiesController < ApplicationController\n wrap_parameters :entropy, include: [ :auto_postpone_period_"
},
{
"path": "app/controllers/account/exports_controller.rb",
"chars": 1152,
"preview": "class Account::ExportsController < ApplicationController\n before_action :ensure_admin_or_owner\n before_action :ensure_"
},
{
"path": "app/controllers/account/imports_controller.rb",
"chars": 1064,
"preview": "class Account::ImportsController < ApplicationController\n layout \"public\"\n\n disallow_account_scope only: %i[ new creat"
},
{
"path": "app/controllers/account/join_codes_controller.rb",
"chars": 1016,
"preview": "class Account::JoinCodesController < ApplicationController\n wrap_parameters :account_join_code, include: %i[ usage_limi"
},
{
"path": "app/controllers/account/settings_controller.rb",
"chars": 676,
"preview": "class Account::SettingsController < ApplicationController\n wrap_parameters :account, include: %i[ name ]\n\n before_acti"
},
{
"path": "app/controllers/admin_controller.rb",
"chars": 105,
"preview": "class AdminController < ApplicationController\n disallow_account_scope\n before_action :ensure_staff\nend\n"
},
{
"path": "app/controllers/application_controller.rb",
"chars": 377,
"preview": "class ApplicationController < ActionController::Base\n include Authentication\n include Authorization\n include BlockSea"
},
{
"path": "app/controllers/boards/columns/closeds_controller.rb",
"chars": 234,
"preview": "class Boards::Columns::ClosedsController < ApplicationController\n include BoardScoped\n\n def show\n set_page_and_extr"
},
{
"path": "app/controllers/boards/columns/not_nows_controller.rb",
"chars": 222,
"preview": "class Boards::Columns::NotNowsController < ApplicationController\n include BoardScoped\n\n def show\n set_page_and_extr"
},
{
"path": "app/controllers/boards/columns/streams_controller.rb",
"chars": 246,
"preview": "class Boards::Columns::StreamsController < ApplicationController\n include BoardScoped\n\n def show\n set_page_and_extr"
},
{
"path": "app/controllers/boards/columns_controller.rb",
"chars": 1165,
"preview": "class Boards::ColumnsController < ApplicationController\n wrap_parameters :column, include: %i[ name color ]\n\n include "
},
{
"path": "app/controllers/boards/entropies_controller.rb",
"chars": 550,
"preview": "class Boards::EntropiesController < ApplicationController\n wrap_parameters :board, include: [ :auto_postpone_period_in_"
},
{
"path": "app/controllers/boards/involvements_controller.rb",
"chars": 305,
"preview": "class Boards::InvolvementsController < ApplicationController\n include BoardScoped\n\n def update\n @board.access_for(C"
},
{
"path": "app/controllers/boards/publications_controller.rb",
"chars": 498,
"preview": "class Boards::PublicationsController < ApplicationController\n include BoardScoped\n\n before_action :ensure_permission_t"
},
{
"path": "app/controllers/boards_controller.rb",
"chars": 2785,
"preview": "class BoardsController < ApplicationController\n wrap_parameters :board, include: %i[ name all_access auto_postpone_peri"
},
{
"path": "app/controllers/cards/assignments_controller.rb",
"chars": 683,
"preview": "class Cards::AssignmentsController < ApplicationController\n include CardScoped\n\n def new\n @assigned_to = @card.assi"
},
{
"path": "app/controllers/cards/boards_controller.rb",
"chars": 537,
"preview": "class Cards::BoardsController < ApplicationController\n include BoardScoped\n\n skip_before_action :set_board, only: %i[ "
},
{
"path": "app/controllers/cards/closures_controller.rb",
"chars": 647,
"preview": "class Cards::ClosuresController < ApplicationController\n include CardScoped\n\n def create\n capture_card_location\n "
},
{
"path": "app/controllers/cards/columns_controller.rb",
"chars": 237,
"preview": "class Cards::ColumnsController < ApplicationController\n def edit\n @card = Current.user.accessible_cards.find_by!(num"
},
{
"path": "app/controllers/cards/comments/reactions_controller.rb",
"chars": 1221,
"preview": "class Cards::Comments::ReactionsController < ApplicationController\n wrap_parameters :reaction, include: %i[ content ]\n\n"
},
{
"path": "app/controllers/cards/comments_controller.rb",
"chars": 1370,
"preview": "class Cards::CommentsController < ApplicationController\n wrap_parameters :comment, include: %i[ body created_at ]\n inc"
},
{
"path": "app/controllers/cards/drafts_controller.rb",
"chars": 232,
"preview": "class Cards::DraftsController < ApplicationController\n include CardScoped\n\n before_action :redirect_if_published\n\n de"
},
{
"path": "app/controllers/cards/goldnesses_controller.rb",
"chars": 414,
"preview": "class Cards::GoldnessesController < ApplicationController\n include CardScoped\n\n def create\n @card.gild\n\n respond"
},
{
"path": "app/controllers/cards/images_controller.rb",
"chars": 243,
"preview": "class Cards::ImagesController < ApplicationController\n include CardScoped\n\n def destroy\n @card.image.purge_later\n\n "
},
{
"path": "app/controllers/cards/not_nows_controller.rb",
"chars": 275,
"preview": "class Cards::NotNowsController < ApplicationController\n include CardScoped\n\n def create\n capture_card_location\n "
},
{
"path": "app/controllers/cards/pins_controller.rb",
"chars": 1048,
"preview": "class Cards::PinsController < ApplicationController\n include CardScoped\n\n def show\n fresh_when etag: @card.pin_for("
},
{
"path": "app/controllers/cards/previews_controller.rb",
"chars": 197,
"preview": "class Cards::PreviewsController < ApplicationController\n include FilterScoped\n\n before_action :set_filter, only: :inde"
},
{
"path": "app/controllers/cards/publishes_controller.rb",
"chars": 527,
"preview": "class Cards::PublishesController < ApplicationController\n include CardScoped\n\n def create\n @card.publish\n\n respo"
},
{
"path": "app/controllers/cards/reactions_controller.rb",
"chars": 1092,
"preview": "class Cards::ReactionsController < ApplicationController\n wrap_parameters :reaction, include: %i[ content ]\n\n include "
},
{
"path": "app/controllers/cards/readings_controller.rb",
"chars": 558,
"preview": "class Cards::ReadingsController < ApplicationController\n include CardScoped\n\n def create\n @notification = @card.rea"
},
{
"path": "app/controllers/cards/self_assignments_controller.rb",
"chars": 472,
"preview": "class Cards::SelfAssignmentsController < ApplicationController\n include CardScoped\n\n def create\n if @card.toggle_as"
},
{
"path": "app/controllers/cards/steps_controller.rb",
"chars": 975,
"preview": "class Cards::StepsController < ApplicationController\n wrap_parameters :step, include: %i[ content completed ]\n\n includ"
},
{
"path": "app/controllers/cards/taggings_controller.rb",
"chars": 548,
"preview": "class Cards::TaggingsController < ApplicationController\n include CardScoped\n\n def new\n @tagged_with = @card.tags.al"
},
{
"path": "app/controllers/cards/triages_controller.rb",
"chars": 469,
"preview": "class Cards::TriagesController < ApplicationController\n include CardScoped\n\n def create\n column = @card.board.colum"
},
{
"path": "app/controllers/cards/watches_controller.rb",
"chars": 468,
"preview": "class Cards::WatchesController < ApplicationController\n include CardScoped\n\n def show\n fresh_when etag: @card.watch"
},
{
"path": "app/controllers/cards_controller.rb",
"chars": 1769,
"preview": "class CardsController < ApplicationController\n wrap_parameters :card, include: %i[ title description image created_at l"
},
{
"path": "app/controllers/client_configurations_controller.rb",
"chars": 377,
"preview": "class ClientConfigurationsController < ApplicationController\n skip_before_action :require_account, :require_authenticat"
},
{
"path": "app/controllers/columns/cards/drops/closures_controller.rb",
"chars": 133,
"preview": "class Columns::Cards::Drops::ClosuresController < ApplicationController\n include CardScoped\n\n def create\n @card.clo"
},
{
"path": "app/controllers/columns/cards/drops/columns_controller.rb",
"chars": 206,
"preview": "class Columns::Cards::Drops::ColumnsController < ApplicationController\n include CardScoped\n\n def create\n @column = "
},
{
"path": "app/controllers/columns/cards/drops/not_nows_controller.rb",
"chars": 135,
"preview": "class Columns::Cards::Drops::NotNowsController < ApplicationController\n include CardScoped\n\n def create\n @card.post"
},
{
"path": "app/controllers/columns/cards/drops/streams_controller.rb",
"chars": 238,
"preview": "class Columns::Cards::Drops::StreamsController < ApplicationController\n include CardScoped\n\n def create\n @card.send"
},
{
"path": "app/controllers/columns/left_positions_controller.rb",
"chars": 269,
"preview": "class Columns::LeftPositionsController < ApplicationController\n include ColumnScoped\n\n def create\n @left_column = @"
},
{
"path": "app/controllers/columns/right_positions_controller.rb",
"chars": 273,
"preview": "class Columns::RightPositionsController < ApplicationController\n include ColumnScoped\n\n def create\n @right_column ="
},
{
"path": "app/controllers/concerns/authentication/via_magic_link.rb",
"chars": 2233,
"preview": "module Authentication::ViaMagicLink\n extend ActiveSupport::Concern\n\n included do\n after_action :ensure_development_"
},
{
"path": "app/controllers/concerns/authentication.rb",
"chars": 2901,
"preview": "module Authentication\n extend ActiveSupport::Concern\n\n included do\n before_action :require_account # Checking and s"
},
{
"path": "app/controllers/concerns/authorization.rb",
"chars": 1103,
"preview": "module Authorization\n extend ActiveSupport::Concern\n\n included do\n before_action :ensure_can_access_account, if: :a"
},
{
"path": "app/controllers/concerns/block_search_engine_indexing.rb",
"chars": 340,
"preview": "# Tell crawlers like Googlebot to drop pages entirely from search results, even\n# if other sites link to it\nmodule Block"
},
{
"path": "app/controllers/concerns/board_scoped.rb",
"chars": 341,
"preview": "module BoardScoped\n extend ActiveSupport::Concern\n\n included do\n before_action :set_board\n end\n\n private\n def "
},
{
"path": "app/controllers/concerns/card_scoped.rb",
"chars": 780,
"preview": "module CardScoped\n extend ActiveSupport::Concern\n\n included do\n before_action :set_card, :set_board\n end\n\n privat"
},
{
"path": "app/controllers/concerns/column_scoped.rb",
"chars": 218,
"preview": "module ColumnScoped\n extend ActiveSupport::Concern\n\n included do\n before_action :set_column\n end\n\n private\n de"
},
{
"path": "app/controllers/concerns/current_request.rb",
"chars": 323,
"preview": "module CurrentRequest\n extend ActiveSupport::Concern\n\n included do\n before_action do\n Current.http_method = re"
},
{
"path": "app/controllers/concerns/current_timezone.rb",
"chars": 545,
"preview": "# FIXME: This should move upstream to Rails. It's a good pattern.\nmodule CurrentTimezone\n extend ActiveSupport::Concern"
},
{
"path": "app/controllers/concerns/day_timelines_scoped.rb",
"chars": 438,
"preview": "module DayTimelinesScoped\n extend ActiveSupport::Concern\n\n included do\n include FilterScoped\n\n before_action :se"
},
{
"path": "app/controllers/concerns/filter_scoped.rb",
"chars": 712,
"preview": "module FilterScoped\n extend ActiveSupport::Concern\n\n included do\n before_action :set_filter\n before_action :set_"
},
{
"path": "app/controllers/concerns/request_forgery_protection.rb",
"chars": 335,
"preview": "module RequestForgeryProtection\n extend ActiveSupport::Concern\n\n included do\n protect_from_forgery using: :header_o"
},
{
"path": "app/controllers/concerns/routing_headers.rb",
"chars": 238,
"preview": "module RoutingHeaders\n extend ActiveSupport::Concern\n\n included do\n before_action :set_target_header\n end\n\n priva"
},
{
"path": "app/controllers/concerns/set_platform.rb",
"chars": 239,
"preview": "module SetPlatform\n extend ActiveSupport::Concern\n\n included do\n helper_method :platform\n end\n\n private\n def p"
},
{
"path": "app/controllers/concerns/turbo_flash.rb",
"chars": 278,
"preview": "module TurboFlash\n extend ActiveSupport::Concern\n\n included do\n helper_method :turbo_stream_flash\n end\n\n private\n"
},
{
"path": "app/controllers/concerns/view_transitions.rb",
"chars": 374,
"preview": "# FIXME: Upstream this fix to turbo-rails\nmodule ViewTransitions\n extend ActiveSupport::Concern\n\n included do\n befo"
},
{
"path": "app/controllers/events/day_timeline/columns_controller.rb",
"chars": 470,
"preview": "class Events::DayTimeline::ColumnsController < ApplicationController\n include DayTimelinesScoped\n\n before_action :ensu"
},
{
"path": "app/controllers/events/days_controller.rb",
"chars": 134,
"preview": "class Events::DaysController < ApplicationController\n include DayTimelinesScoped\n\n def index\n fresh_when @day_timel"
},
{
"path": "app/controllers/events_controller.rb",
"chars": 128,
"preview": "class EventsController < ApplicationController\n include DayTimelinesScoped\n\n def index\n fresh_when @day_timeline\n "
},
{
"path": "app/controllers/filters/settings_refreshes_controller.rb",
"chars": 114,
"preview": "class Filters::SettingsRefreshesController < ApplicationController\n include FilterScoped\n\n def create\n end\nend\n"
},
{
"path": "app/controllers/filters_controller.rb",
"chars": 434,
"preview": "class FiltersController < ApplicationController\n before_action :set_filters\n\n def create\n @filter = Current.user.fi"
},
{
"path": "app/controllers/join_codes_controller.rb",
"chars": 1563,
"preview": "class JoinCodesController < ApplicationController\n allow_unauthenticated_access\n rate_limit to: 10, within: 3.minutes,"
},
{
"path": "app/controllers/landings_controller.rb",
"chars": 236,
"preview": "class LandingsController < ApplicationController\n def show\n flash.keep(:welcome_letter)\n\n if Current.user.boards."
},
{
"path": "app/controllers/my/access_tokens_controller.rb",
"chars": 1548,
"preview": "class My::AccessTokensController < ApplicationController\n wrap_parameters :access_token, include: %i[ description permi"
},
{
"path": "app/controllers/my/identities_controller.rb",
"chars": 135,
"preview": "class My::IdentitiesController < ApplicationController\n disallow_account_scope\n\n def show\n @identity = Current.iden"
},
{
"path": "app/controllers/my/menus_controller.rb",
"chars": 402,
"preview": "class My::MenusController < ApplicationController\n def show\n @filters = Current.user.filters.all\n @boards = Curre"
},
{
"path": "app/controllers/my/passkey_challenges_controller.rb",
"chars": 192,
"preview": "class My::PasskeyChallengesController < ActionPack::Passkey::ChallengesController\n include Authentication\n include Aut"
},
{
"path": "app/controllers/my/passkeys_controller.rb",
"chars": 791,
"preview": "class My::PasskeysController < ApplicationController\n include ActionPack::Passkey::Request\n\n before_action :set_passke"
},
{
"path": "app/controllers/my/pins_controller.rb",
"chars": 316,
"preview": "class My::PinsController < ApplicationController\n def index\n @pins = user_pins\n fresh_when etag: [ @pins, @pins.c"
},
{
"path": "app/controllers/my/timezones_controller.rb",
"chars": 213,
"preview": "class My::TimezonesController < ApplicationController\n def update\n Current.user.settings.update!(timezone_name: time"
},
{
"path": "app/controllers/notifications/bulk_readings_controller.rb",
"chars": 414,
"preview": "class Notifications::BulkReadingsController < ApplicationController\n def create\n Current.user.notifications.unread.r"
},
{
"path": "app/controllers/notifications/readings_controller.rb",
"chars": 514,
"preview": "class Notifications::ReadingsController < ApplicationController\n def create\n @notification = Current.user.notificati"
},
{
"path": "app/controllers/notifications/settings_controller.rb",
"chars": 635,
"preview": "class Notifications::SettingsController < ApplicationController\n wrap_parameters :user_settings, include: %i[ bundle_em"
},
{
"path": "app/controllers/notifications/trays_controller.rb",
"chars": 794,
"preview": "class Notifications::TraysController < ApplicationController\n MAX_ENTRIES_LIMIT = 100\n\n def show\n @notifications = "
},
{
"path": "app/controllers/notifications/unsubscribes_controller.rb",
"chars": 526,
"preview": "class Notifications::UnsubscribesController < ApplicationController\n allow_unauthenticated_access\n skip_forgery_protec"
},
{
"path": "app/controllers/notifications_controller.rb",
"chars": 673,
"preview": "class NotificationsController < ApplicationController\n MAX_UNREAD_NOTIFICATIONS = 500\n MAX_UNREAD_NOTIFICATIONS_VIA_AP"
},
{
"path": "app/controllers/prompts/boards/users_controller.rb",
"chars": 219,
"preview": "class Prompts::Boards::UsersController < ApplicationController\n include BoardScoped\n\n def index\n @users = @board.us"
},
{
"path": "app/controllers/prompts/cards_controller.rb",
"chars": 812,
"preview": "class Prompts::CardsController < ApplicationController\n MAX_RESULTS = 10\n\n def index\n @cards = if filter_param.pres"
},
{
"path": "app/controllers/prompts/tags_controller.rb",
"chars": 190,
"preview": "class Prompts::TagsController < ApplicationController\n def index\n @tags = Current.account.tags.all.alphabetically\n\n "
},
{
"path": "app/controllers/prompts/users_controller.rb",
"chars": 197,
"preview": "class Prompts::UsersController < ApplicationController\n def index\n @users = Current.account.users.active.alphabetica"
},
{
"path": "app/controllers/public/base_controller.rb",
"chars": 675,
"preview": "class Public::BaseController < ApplicationController\n allow_unauthenticated_access\n\n before_action :set_board, :set_ca"
},
{
"path": "app/controllers/public/boards/columns/closeds_controller.rb",
"chars": 185,
"preview": "class Public::Boards::Columns::ClosedsController < Public::BaseController\n def show\n set_page_and_extract_portion_fr"
},
{
"path": "app/controllers/public/boards/columns/not_nows_controller.rb",
"chars": 163,
"preview": "class Public::Boards::Columns::NotNowsController < Public::BaseController\n def show\n set_page_and_extract_portion_fr"
},
{
"path": "app/controllers/public/boards/columns/streams_controller.rb",
"chars": 187,
"preview": "class Public::Boards::Columns::StreamsController < Public::BaseController\n def show\n set_page_and_extract_portion_fr"
},
{
"path": "app/controllers/public/boards/columns_controller.rb",
"chars": 421,
"preview": "class Public::Boards::ColumnsController < Public::BaseController\n before_action :set_column\n\n def show\n set_page_an"
},
{
"path": "app/controllers/public/boards_controller.rb",
"chars": 169,
"preview": "class Public::BoardsController < Public::BaseController\n def show\n set_page_and_extract_portion_from @board.cards.aw"
},
{
"path": "app/controllers/public/cards_controller.rb",
"chars": 76,
"preview": "class Public::CardsController < Public::BaseController\n def show\n end\nend\n"
},
{
"path": "app/controllers/pwa_controller.rb",
"chars": 210,
"preview": "class PwaController < ApplicationController\n disallow_account_scope\n skip_forgery_protection\n\n # We need a stable URL"
},
{
"path": "app/controllers/qr_codes_controller.rb",
"chars": 323,
"preview": "class QrCodesController < ApplicationController\n allow_unauthenticated_access\n\n def show\n expires_in 1.year, public"
},
{
"path": "app/controllers/searches/queries_controller.rb",
"chars": 139,
"preview": "class Searches::QueriesController < ApplicationController\n def create\n Current.user.remember_search(params[:q])\n "
},
{
"path": "app/controllers/searches_controller.rb",
"chars": 847,
"preview": "class SearchesController < ApplicationController\n include Turbo::DriveHelper\n\n def show\n @query = params[:q].blank?"
},
{
"path": "app/controllers/sessions/magic_links_controller.rb",
"chars": 2749,
"preview": "class Sessions::MagicLinksController < ApplicationController\n disallow_account_scope\n require_unauthenticated_access\n "
},
{
"path": "app/controllers/sessions/menus_controller.rb",
"chars": 264,
"preview": "class Sessions::MenusController < ApplicationController\n disallow_account_scope\n\n layout \"public\"\n\n def show\n @acc"
},
{
"path": "app/controllers/sessions/passkeys_controller.rb",
"chars": 1162,
"preview": "class Sessions::PasskeysController < ApplicationController\n include ActionPack::Passkey::Request\n\n disallow_account_sc"
},
{
"path": "app/controllers/sessions/transfers_controller.rb",
"chars": 352,
"preview": "class Sessions::TransfersController < ApplicationController\n disallow_account_scope\n require_unauthenticated_access\n\n "
},
{
"path": "app/controllers/sessions_controller.rb",
"chars": 2032,
"preview": "class SessionsController < ApplicationController\n include ActionPack::Passkey::Request\n\n disallow_account_scope\n requ"
},
{
"path": "app/controllers/signups/completions_controller.rb",
"chars": 1013,
"preview": "class Signups::CompletionsController < ApplicationController\n wrap_parameters :signup, include: %i[ full_name ]\n\n layo"
},
{
"path": "app/controllers/signups_controller.rb",
"chars": 940,
"preview": "class SignupsController < ApplicationController\n wrap_parameters :signup, include: %i[ email_address ]\n\n disallow_acco"
},
{
"path": "app/controllers/tags_controller.rb",
"chars": 141,
"preview": "class TagsController < ApplicationController\n def index\n set_page_and_extract_portion_from Current.account.tags.alph"
},
{
"path": "app/controllers/users/avatars_controller.rb",
"chars": 1085,
"preview": "class Users::AvatarsController < ApplicationController\n allow_unauthenticated_access only: :show\n\n before_action :set_"
},
{
"path": "app/controllers/users/data_exports_controller.rb",
"chars": 879,
"preview": "class Users::DataExportsController < ApplicationController\n before_action :set_user\n before_action :ensure_current_use"
},
{
"path": "app/controllers/users/email_addresses/confirmations_controller.rb",
"chars": 685,
"preview": "class Users::EmailAddresses::ConfirmationsController < ApplicationController\n allow_unauthenticated_access\n\n before_ac"
},
{
"path": "app/controllers/users/email_addresses_controller.rb",
"chars": 695,
"preview": "class Users::EmailAddressesController < ApplicationController\n before_action :set_user\n rate_limit to: 5, within: 1.ho"
}
]
// ... and 1325 more files (download for full content)
About this extraction
This page contains the full source code of the basecamp/fizzy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1525 files (2.1 MB), approximately 618.6k tokens, and a symbol index with 3236 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.