{
  "id": "doctolib-doctolib/wiggly-noodling-corbato-subagent-a8c8f970",
  "slug": "wiggly-noodling-corbato-subagent-a8c8f970",
  "title": "Session: wiggly-noodling-corbato-subagent-a8c8f970 — 2026-03-12",
  "type": "source",
  "project": "doctolib-doctolib",
  "date": "2026-03-12",
  "started": "2026-03-12T10:32:15.500000+00:00",
  "ended": "2026-03-12T10:36:06.046000+00:00",
  "model": "claude-haiku-4-5-20251001",
  "cwd": "/Users/USER/workspace/doctolib/doctolib",
  "git_branch": "master",
  "permission_mode": "",
  "user_messages": 1,
  "tool_calls": 29,
  "tools_used": [
    "Bash",
    "Grep",
    "Read",
    "mcp__excalidraw__read_me",
    "mcp__excalidraw__create_view"
  ],
  "is_subagent": true,
  "wikilinks_out": [],
  "body_text": "Project: doctolib-doctolib · Branch: master · Mode:  · Model: claude-haiku-4-5-20251001\n\nStats: 1 user messages, 29 tool calls, tools used: Bash, Grep, Read, mcp__excalidraw__read_me, mcp__excalidraw__create_view.\n\nConversation\n\nTurn 1 — User\n\nFind where Rails serializes feature switches into frontend CONSTANTS (JavaScript). Specifically how the send_to_front: true flag in config/feature_switches.yml causes a feature switch like nhr_document_list to become NHR_DOCUMENT_LIST_ENABLED in the frontend CONSTANTS object.\n\nLook for:\n1. Ruby code that reads config/feature_switches.yml and generates frontend constants\n2. Where CONSTANTS is built/serialized in a view or serializer for the Rails app\n3. How cohort-type feature switches with send_to_front: true are evaluated for the current account\n\nKey questions: \n- Does the Rails backend check the cohort at request time and set the constant, OR\n- Is the feature switch globally enabled in the dev environment somehow?\n- Is there a way to override feature switches locally in development that might cause nhr_document_list to be always true?\n\nSearch in app/, engines/, lib/ directories for Ruby code related to feature switches and frontend constants serialization.\n\nTurn 1 — Assistant\n\nI'll help you find how Rails serializes feature switches into frontend CONSTANTS. Let me search for the relevant code.\n\nTools used:\n\n- Bash: find /Users/USER/workspace/doctolib/doctolib -type f -name \"feature_switches.yml\" | head -20\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/test/fixtures/files/application_preference/feature_switches.yml\n/Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml\n/Users/USER/workspace/doctolib/doctolib/lib/data/organization/feature_switches.yml\n\nNow let me search for code that processes feature switches and sends them to the frontend:\n\nTools used:\n\n- Grep: send_to_front|NHR_DOCUMENT_LIST_ENABLED|CONSTANTS.feature in /Users/USER/workspace/doctolib/doctolib\n\nTool results:\n\n  → result (ok): Found 9 files\ntest/static_checks/feature_switches_test.rb\ntest/services/countries/feature_test.rb\napp/services/countries/feature.rb\nengines/patient_mobile_app/test/controllers/patient_mobile_app/configuration_api/v1/configurations_controller_test.rb\napp/helpers/frontend_constants_helper.rb\ntest/static_checks/country_feature_test.rb\ntest/helpers/frontend_constants_helper_test.rb\nengines/feature_switches/test/helpers/feature_switches/feature_switches_helper_test.rb\nengines/feature_switches/app/hel\n…(truncated, 48 more chars)\n\nPerfect! Let me examine the key files:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb\n\nTool results:\n\n  → result (ok): 1→# frozen_string_literal: true\n     2→\n     3→module FrontendConstantsHelper\n     4→  # rubocop:todo DoctolibInternal/NoCountrySpecificLogic\n     5→  CONSTANTS = {\n     6→    AGENDA_DEFAULT_MAX_TIME: Preferences::AccountPreferences::Api::DEFAULT_MAX_TIME,\n     7→    AGENDA_DEFAULT_MIN_TIME: Preferences::AccountPreferences::Api::DEFAULT_MIN_TIME,\n     8→    AMELI_WEB_SERVICE_URL: ENV.fetch('AMELI_WEB_SERVICE_URL', nil),\n     9→    APPOINTMENT_PAGINATION_SIZE: Appointment::PAGINATION_SIZE,\n    10\n…(truncated, 18601 more chars)\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/engines/feature_switches/app/helpers/feature_switches/feature_switches_helper.rb\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml\n\nTool results:\n\n  → result (ERROR): File content (441.4KB) exceeds maximum allowed size (256KB). Use offset and limit parameters to read specific portions of the file, or search for specific content instead of reading the whole file.\n\nTool results:\n\n  → result (ok): 1→# frozen_string_literal: true\n     2→\n     3→module FeatureSwitches::FeatureSwitchesHelper\n     4→  # rubocop:disable Metrics/MethodLength\n     5→  def list_feature_switches(context: {}, frontend_only: false)\n     6→    global_toggles = {}\n     7→    global_values = {}\n     8→    global_factors = {}\n     9→\n    10→    country_toggles = {}\n    11→    country_values = {}\n    12→    country_factors = {}\n    13→\n    14→    # We skip type requirements to ease migration pains. We cannot know ahead o\n…(truncated, 2837 more chars)\n\nLet me search for ApplicationPreference and the feature evaluation logic:\n\nTools used:\n\n- Bash: grep -r \"class ApplicationPreference\" /Users/USER/workspace/doctolib/doctolib/app --include=\".rb\" | head -5\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/app/models/application_preference/evaluation.rb:class ApplicationPreference::Evaluation < Db4Record\n/Users/USER/workspace/doctolib/doctolib/app/models/application_preference/warn_expired_feature_switches.rb:class ApplicationPreference::WarnExpiredFeatureSwitches\n/Users/USER/workspace/doctolib/doctolib/app/models/application_preference/trim_feature_switch_entries.rb:class ApplicationPreference::TrimFeatureSwitchEntries\n/Users/USER/workspace/doctolib/doctoli\n…(truncated, 288 more chars)\n\nNow let me look at the ApplicationPreference model to understand how features are loaded and evaluated:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb (1–200)\n\nTool results:\n\n  → result (ok): 1→# frozen_string_literal: true\n     2→\n     3→class ApplicationPreference < Db1Record\n     4→  data_taxonomy do\n     5→    description('Application feature switches state')\n     6→\n     7→    default(\n     8→      secret: false,\n     9→      system_id: false,\n    10→      health_information_likelihood: :none,\n    11→      data_subject_identity_likelihood: :none,\n    12→      data_subject_info_likelihood: :none,\n    13→      data_subjects: [:not_applicable],\n    14→      sensitive_likelihood: :n\n…(truncated, 8129 more chars)\n\nTools used:\n\n- Bash: grep -n \"FEATURES\\|feature_enabled?\" /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb | head -30\n\nTool results:\n\n  → result (ok): 31:  GROUPED_FEATURES =\n34:  GROUPED_CIRCUIT_BREAKER_FEATURES =\n39:  FEATURES =\n40:    GROUPED_FEATURES\n43:      .merge!(GROUPED_CIRCUIT_BREAKER_FEATURES.map { |_, v| v['features'] }.reduce(:merge))\n46:  COHORT_FEATURE_KEYS = ApplicationPreference::FEATURES.select { |_, metadata| metadata['cohort_types'].present? }.keys\n54:  store_accessor :features_enabled, FEATURES.keys, FEATURES.keys.map { |f| \"#{f}_cohorts\" }\n75:             :feature_enabled?,\n168:    FEATURES.each_key do |feature|\n171:   \n…(truncated, 1041 more chars)\n\nLet me look at the feature_enabled? method in detail:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb (299–399)\n\nTool results:\n\n  → result (ok): 299→  def feature_enabled?(feature, context: nil)\n   300→    return evaluate_with_context(feature, context) if cohort_feature?(feature)\n   301→    return apply_factor?(feature) if factor_feature_switch?(feature)\n   302→    feature_value(feature).present?\n   303→  end\n   304→\n   305→  def feature_enabled_for_country?(feature, country)\n   306→    raise 'Please use feature_enabled! method instead.' unless country_feature_switch?(feature)\n   307→\n   308→    return apply_factor?(feature, country: cou\n…(truncated, 4417 more chars)\n\nNow let me search for evaluate_with_context and cohort evaluation:\n\nTools used:\n\n- Bash: grep -n \"evaluate_with_context\\|cohort_feature?\" /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb | head -20\n\nTool results:\n\n  → result (ok): 85:             :cohort_feature?,\n247:      elsif cohort_feature?(feature)\n300:    return evaluate_with_context(feature, context) if cohort_feature?(feature)\n470:  def cohort_feature?(feature)\n475:    raise \"Expected #{feature} to be a cohort feature but was not\" unless cohort_feature?(feature)\n517:      raise \"Expected #{feature} to be a cohort feature but was not\" unless cohort_feature?(feature)\n523:  def evaluate_with_context(feature, context)\n\nLet me look at the evaluate_with_context method:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb (523–573)\n\nTool results:\n\n  → result (ok): 523→  def evaluate_with_context(feature, context)\n   524→    context ||= {} if Stagery.stage.production?\n   525→\n   526→    unless context.is_a?(Hash)\n   527→      raise \"#{feature}: You must supply a context to evaluate this feature flag (received #{context.class})\"\n   528→    end\n   529→\n   530→    context[:cohorts] ||= {}\n   531→\n   532→    required_types = cohort_types(feature)\n   533→    context_check_required_types!(feature, context, required_types) unless context[:skip_cohort_type_require\n…(truncated, 1843 more chars)\n\nNow let me search for the key place where nhr_document_list is checked - looking at the feature_switches.yml config:\n\nTools used:\n\n- Bash: grep -A 5 -B 2 \"nhr_document_list\" /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml\n\nTool results:\n\n  → result (ok): team: FIRE\n    value_in_test: false\n  nhr_document_list:\n    cohort_types:\n      - account\n    default: false\n    details: Enables the NHR (National Health Records / DMP) document list modal for\n      healthcare professionals. Part of DMP 3.1a certification.\n\nLet me check if nhr_document_list has send_to_front:\n\nTools used:\n\n- Bash: grep -A 10 \"nhr_document_list:\" /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml\n\nTool results:\n\n  → result (ok): nhr_document_list:\n    cohort_types:\n      - account\n    default: false\n    details: Enables the NHR (National Health Records / DMP) document list modal for\n      healthcare professionals. Part of DMP 3.1a certification.\n    expire_at: 2027-06-01\n    message: NHR document list — DMP 3.1a feature rollout\n    send_to_front: true\n    team: FIRE\n    value_in_test: false\n\nPerfect! Now let me look at how the cohort context is built in the frontend_constants_helper:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb (282–302)\n\nTool results:\n\n  → result (ok): 282→  def build_cohort_context\n   283→    resources = []\n   284→\n   285→    # Cohort feature switches will be active if the account or any organization is part of the cohort\n   286→    # They will also be active if the meta-cohort INCLUDE_ALL is included in the feature cohorts,\n   287→    # even outside of the pro domain.\n   288→    # To send organization-specific cohort feature flags to the front, evaluate them with a custom\n   289→    # context in your FrontendConstants, or as part of your Rea\n…(truncated, 1004 more chars)\n\nNow let me search for ApplicationPreferences::ContextResolution:\n\nTools used:\n\n- Bash: find /Users/USER/workspace/doctolib/doctolib -type f -name \".rb\" -path \"/application_preferences\" | grep -i \"context_resolution\\|context\"\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/test/lib/application_preferences/context_resolution_test.rb\n/Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb\n\nLet me look at context resolution:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb\n\nTool results:\n\n  → result (ok): 1→# frozen_string_literal: true\n     2→\n     3→module ApplicationPreferences\n     4→  module ContextResolution\n     5→    RESOURCE_TYPE_MAP = {\n     6→      'Account' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT],\n     7→      'Authentication::AccountContext' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT],\n     8→      'Legal::Account' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT],\n     9→      'StaffAccounts::Account' => Cohorts::Resolution::RESOURCE_TYPES[:ACCOUNT],\n    10→      'Teles\n…(truncated, 2963 more chars)\n\nNow let me check where the frontend constants are injected into views:\n\nTools used:\n\n- Bash: grep -r \"inject_constants\" /Users/USER/workspace/doctolib/doctolib --include=\".erb\" | head -20\n\nTool results:\n\n  → result (ok): (Bash completed with no output)\n\nLet me search for where inject_constants is used:\n\nTools used:\n\n- Bash: grep -r \"inject_constants\" /Users/USER/workspace/doctolib/doctolib/app --include=\".rb\" | head -20\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb:  def inject_constants\n\nLet me search in view files:\n\nTools used:\n\n- Bash: find /Users/USER/workspace/doctolib/doctolib -name \".html\" -o -name \".haml\" | xargs grep -l \"inject_constants\" 2>/dev/null | head -20\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/app/views/configuration/telehealth/enroll.html+iframe.slim\n/Users/USER/workspace/doctolib/doctolib/app/views/secretary/front_desk/show.html.slim\n/Users/USER/workspace/doctolib/doctolib/app/views/layouts/configuration.html.slim\n/Users/USER/workspace/doctolib/doctolib/app/views/layouts/billing_mobile_offline.html.slim\n/Users/USER/workspace/doctolib/doctolib/app/views/layouts/pharmacy.html.slim\n/Users/USER/workspace/doctolib/doctolib/app/views/layouts/billeo.\n…(truncated, 1334 more chars)\n\nLet me look at one of these views:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/views/layouts/application.html.slim (1–100)\n\nTool results:\n\n  → result (ok): 1→- if Doctowebsite::Api.doctowebsite_access?(account_id: current_account_or_nil&.id)\n     2→  - content_for(:left_navigation_bar) do\n     3→    = react_component(React::FreemiumLeftNavigationBar, is_patient_website: true)\n     4→\n     5→doctype html\n     6→/!\n     7→  Looking at our code ?\n     8→  Take a closer look – we offer rewards of up to €50,000 for impactful security bug discoveries.\n     9→  https://yeswehack.com/programs/doctolib-public-bug-bounty-program\n    10→\n    11→html lang=I18n\n…(truncated, 5192 more chars)\n\nPerfect! Now let me look at how the feature switch evaluation checks for INCLUDE_ALL in development:\n\nTools used:\n\n- Bash: grep -r \"INCLUDE_ALL\" /Users/USER/workspace/doctolib/doctolib/lib /Users/USER/workspace/doctolib/doctolib/app --include=\".rb\" | head -30\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/lib/tasks/billeo.rb:      [ApplicationPreference::INCLUDE_ALL_COHORT_ID],\n/Users/USER/workspace/doctolib/doctolib/app/mailers/account_mailer.rb:            deprecation__soft_deleted: HcpAccount::Api::Account::SOFT_DELETED__INCLUDE_ALL,\n/Users/USER/workspace/doctolib/doctolib/app/models/account.rb:        deprecation__soft_deleted: HcpAccount::Api::Account::SOFT_DELETED__INCLUDE_ALL,\n/Users/USER/workspace/doctolib/doctolib/app/models/application_preference.\n…(truncated, 3323 more chars)\n\nNow let me look for where cohorts might be set or managed in development:\n\nTools used:\n\n- Bash: find /Users/USER/workspace/doctolib/doctolib -type f -name \".rb\" -path \"/engines/cohorts\" | head -20\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/models/cohorts/membership.rb\n/Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/models/cohorts/history.rb\n/Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/models/cohorts/csv.rb\n/Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/models/cohorts/expired.rb\n/Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/models/cohorts/resource.rb\n/Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/models/coh\n…(truncated, 1353 more chars)\n\nLet me look at how cohorts are resolved and how it determines if an account is in a cohort:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/engines/cohorts/app/public/cohorts/resolution.rb (1–150)\n\nTool results:\n\n  → result (ok): 1→# frozen_string_literal: true\n     2→\n     3→module Cohorts\n     4→  module Resolution\n     5→    RESOURCE_TYPES = Cohorts::Cohort.resource_types\n     6→\n     7→    class << self\n     8→      def cohorts_from_identities(identities)\n     9→        Cohort::Resolution.from_identities(identities)\n    10→      end\n    11→\n    12→      Doctolib::O11y.tracer.add_method_tracer self, :cohorts_from_identities\n    13→\n    14→      def cohorts_from_identity(identity)\n    15→        # uncached; used in adm\n…(truncated, 922 more chars)\n\nNow let me look at the test file to see if there's any special dev setup:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/test/helpers/frontend_constants_helper_test.rb (1–200)\n\nTool results:\n\n  → result (ok): 1→# frozen_string_literal: true\n     2→\n     3→require 'test_helper'\n     4→\n     5→class FrontendConstantsHelperTest < Doctolib::Testing::ActionView::TestCase\n     6→  include AppointmentsHelper\n     7→  include ConfigurationHelper\n     8→  include Cohorts::CohortUtilsTestHelper\n     9→  include Domain\n    10→\n    11→  before do\n    12→    stubs(:country).returns(Country::FR)\n    13→    @test_current_account = nil\n    14→  end\n    15→\n    16→  def current_account_or_nil\n    17→    @test_current\n…(truncated, 8694 more chars)\n\nPerfect! Now let me search for local dev overrides or special development mode handling:\n\nTools used:\n\n- Bash: grep -r \"development\\|local\" /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb | head -20\n\nTool results:\n\n  → result (ok): (Bash completed with no output)\n\nLet me check the view for where assume_or_current_account comes from and if there's a way it bypasses the cohort check in dev:\n\nTools used:\n\n- Bash: grep -r \"desktop_pro_or_mobile_pro_domain?\" /Users/USER/workspace/doctolib/doctolib/app --include=\".rb\" -A 5 | head -50\n\nTool results:\n\n  → result (ok): /Users/USER/workspace/doctolib/doctolib/app/controllers/application_controller.rb:                :desktop_pro_or_mobile_pro_domain?,\n/Users/USER/workspace/doctolib/doctolib/app/controllers/application_controller.rb-                :mobile_version?,\n/Users/USER/workspace/doctolib/doctolib/app/controllers/application_controller.rb-                :tanker_fields,\n/Users/USER/workspace/doctolib/doctolib/app/controllers/application_controller.rb-                :show_pin_set?,\n/Users/USER/workspace/\n…(truncated, 4770 more chars)\n\nLet me look at the domain concern:\n\nTools used:\n\n- Read: /Users/USER/workspace/doctolib/doctolib/app/controllers/concerns/domain.rb\n\nTool results:\n\n  → result (ok): 1→# typed: true\n     2→# frozen_string_literal: true\n     3→\n     4→module Domain\n     5→  extend ActiveSupport::Concern\n     6→  extend T::Sig\n     7→\n     8→  # TODO(i18n): This should come from somewhere within the Country config\n     9→  # rubocop:todo DoctolibInternal/NoCountrySpecificLogic\n    10→  TLD_DOMAIN_ENV_MAP = {\n    11→    'fr' => {\n    12→      'www' => 'WWW_DOMAIN',\n    13→      'pro' => 'PRO_DOMAIN',\n    14→      'admin' => 'ADMIN_DOMAIN',\n    15→      'mobile' => 'MOBILE_DOMAI\n…(truncated, 9631 more chars)\n\nNow let me create a comprehensive diagram showing the flow. First, let me check the mcp__excalidraw tools:\n\nTools used:\n\n- mcp__excalidraw__read_me (inputs: )\n\nTool results:\n\n  → result (ok): # Excalidraw Element Format\n\nThanks for calling read_me! Do NOT call it again in this conversation — you will not see anything new. Now use create_view to draw.\n\nColor Palette (use consistently across all tools)\n\nPrimary Colors\n| Name | Hex | Use |\n|------|-----|-----|\n| Blue | #4a9eed | Primary actions, links, data series 1 |\n| Amber | #f59e0b | Warnings, highlights, data series 2 |\n| Green | #22c55e | Success, positive, data series 3 |\n| Red | #ef4444 | Errors, negative, data se\n…(truncated, 26442 more chars)\n\nNow let me create a comprehensive diagram showing the feature switch serialization flow:\n\nTools used:\n\n- mcp__excalidraw__create_view (inputs: elements)\n\nTool results:\n\n  → result (ok): {\"checkpointId\":\"844105470d184a37a9\"}\n\nPerfect! Now let me create a detailed summary document as a response:\n\nSummary: How Rails Serializes Feature Switches to Frontend CONSTANTS\n\nI've traced the complete flow of how feature switches like nhr_document_list become NHR_DOCUMENT_LIST_ENABLED in the frontend CONSTANTS. Here's what I found:\n\nKey Files Involved\n\n1. Configuration: /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml\n   - Defines all feature switches with metadata including send_to_front: true flag\n   - nhr_document_list has send_to_front: true and cohort_types: [account]\n\n2. Primary Helper: /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb\n   - inject_constants() - Called in views (e.g., /app/views/layouts/application.html.slim line 56)\n   - build_constants() - Merges static CONSTANTS with feature switches and dynamic values\n   - feature_switches_sent_to_front() - Filters features where send_to_front == true\n   - build_cohort_context() - Resolves cohort membership at request time\n   - evaluate_feature_switch_pair() - Converts feature name to constant name (e.g., nhr_document_list → NHR_DOCUMENT_LIST_ENABLED)\n\n3. Feature Model: /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb\n   - FEATURES - Loaded from feature_switches.yml on boot\n   - feature_enabled?(feature, context:) - Evaluates feature with cohort context\n   - evaluate_with_context(feature, context) - Core cohort evaluation logic (line 523-545)\n   - cohort_feature?(feature) - Determines if feature requires cohort evaluation\n\n4. Cohort Resolution: /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb\n   - resolved_cohorts_from_resources(resources) - Maps account/org to cohorts they belong to\n   - evaluation_context() - Returns hash with cohort memberships for evaluation\n\nThe Flow: Request Time Evaluation\n\nHere's how nhr_document_list becomes NHR_DOCUMENT_LIST_ENABLED:\n\nStep 1: View Renders Constants (Request Time)\nView (layout) → calls inject_constants helper\n→ Generates: window.dl_constants = {build_constants().to_json}\n→ Executes BEFORE page load in browser\n\nStep 2: Build Constants Dictionary\nfrontend_constants_helper.rb lines 120-129\nbuild_constants returns:\n{\n  CONSTANTS (static),\n  built_with_account: account.id,\n  engines_frontend_constants,\n  feature_switches_sent_to_front,  # ← FEATURE SWITCHES EVALUATED HERE\n  dynamic_constants,\n  country_features,\n  deprecated_associated_account_fs\n}\n\nStep 3: Filter and Evaluate Cohort Features\nfrontend_constants_helper.rb lines 133-140\nfeature_switches_sent_to_front:\n  1. Filters ApplicationPreference::FEATURES for send_to_front == true\n  2. Builds cohort_context via build_cohort_context()\n  3. For each feature, calls evaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context)\n\nStep 4: Build Cohort Context\nfrontend_constants_helper.rb lines 282-296\nbuild_cohort_context:\n  IF:\n    - assumed_or_current_account.present? (user is logged in)\n    - desktop_pro_or_mobile_pro_domain? (pro or mobile-pro domain)\n  THEN:\n    - Resources = [account, account.all_current_organizations]\n    - Resolve cohorts from these resources\n    - Return evaluation_context with cohort mappings\n  ELSE:\n    - Return empty resources (INCLUDE_ALL still works)\n\nStep 5: Evaluate Feature Switch Pair\nfrontend_constants_helper.rb lines 219-250\nevaluate_feature_switch_pair(feature_name, feature_attributes, cohort_context):\n  1. Checks if per_country, per_speciality, per_ssid flags\n  2. For bare toggle (like nhr_document_list):\n     - Calls: ApplicationPreference.nhr_document_list_enabled?(context: cohort_context)\n  3. Converts feature_name to CONSTANT:\n     - \"nhr_document_list\" → \"NHR_DOCUMENT_LIST_ENABLED\"\n  4. Returns [constant_name, boolean_value]\n\nStep 6: Cohort Evaluation at Feature Level\napplication_preference.rb lines 299-303\nfeature_enabled?(feature, context: nil):\n  IF cohort_feature?(feature):\n    return evaluate_with_context(feature, context)\n  ELSIF factor_feature_switch?:\n    return apply_factor?(feature)\n  ELSE:\n    return feature_value(feature).present?\n\nStep 7: Core Cohort Evaluation\napplication_preference.rb lines 523-545\nevaluate_with_context(feature, context):\n  1. Context contains cohort_id lists for account/org\n  2. Get active_cohorts for the feature from database\n  3. RETURNS TRUE if:\n     a) active_cohorts includes INCLUDE_ALL_COHORT_ID\n     b) active_cohorts intersects with context[:cohorts][:account] or context[:cohorts][:organization]\n  4. RETURNS FALSE otherwise\n\nKey Questions Answered\n\nQ: Does the backend check the cohort at request time?  \nA: YES. The cohort evaluation happens synchronously during view rendering, not on the frontend. The context is built with build_cohort_context(), which resolves actual cohort memberships for the current account.\n\nQ: Is the feature switch globally enabled in dev?  \nA: NO. Feature switches are evaluated per-request based on:\n  - Whether the account is in the feature's assigned cohorts\n  - OR whether the feature has the meta-cohort INCLUDE_ALL_COHORT_ID\n\nQ: Is there a way to override locally in development?  \nA: YES, multiple ways:\n  1. Database: ApplicationPreference.enable_feature!(:nhr_document_list) - enables globally\n  2. Cohorts: Add account/org to the cohort assigned to the feature\n  3. INCLUDE_ALL: Add 'INCLUDE_ALL' cohort ID to nhr_document_list feature cohorts\n  4. Direct assignment: Manually call ApplicationPreference.update_cohorts!(:nhr_document_list, [cohort_id])\n\nDomain Dependency\n\nThe cohort context is ONLY built if BOTH conditions are met:\nif assumed_or_current_account.present? && desktop_pro_or_mobile_pro_domain?\n\nThis means:\n- On PRO domain (ENV['PRO_DOMAIN']) - cohort context is built\n- On MOBILE-PRO domain (ENV['PRO_MOBILE_SUBDOMAIN']) - cohort context is built\n- On WWW domain - cohort features evaluate to FALSE (empty resources)\n- No logged-in account - cohort features evaluate to FALSE\n\nFile Locations (Absolute Paths)\n\n- /Users/USER/workspace/doctolib/doctolib/app/helpers/frontend_constants_helper.rb - Main serialization logic\n- /Users/USER/workspace/doctolib/doctolib/app/models/application_preference.rb - Feature evaluation\n- /Users/USER/workspace/doctolib/doctolib/lib/application_preferences/context_resolution.rb - Cohort resolution\n- /Users/USER/workspace/doctolib/doctolib/config/feature_switches.yml - Feature definitions\n- /Users/USER/workspace/doctolib/doctolib/engines/cohorts/ - Cohort engine (manages membership)\n- /Users/USER/workspace/doctolib/doctolib/app/views/layouts/application.html.slim - View calling inject_constants",
  "sha256": "22b8330ef2cf8831",
  "source_url": "sessions/doctolib-doctolib/2026-03-12T10-32-doctolib-doctolib-wiggly-noodling-corbato-subagent-a8c8f970.html"
}