Drop the JSON source_config column
Source kinds that need configuring will get their own table keyed by
list_id, and build their source from it with Kind.for(list). Text needs
no configuration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LG4iASZnHb1yEM1JAASiWn
change
commit ff57223f91db36cebb5ad6647dfb18f5749c70b1
author Claude <noreply@anthropic.com>
date
parent 8135bacf
diff --git a/AGENTS.md b/AGENTS.md
index ada20df..8236972 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -45,7 +45,7 @@ fly.toml           — Fly config for the main demo and PR previews
 ## Domain model
 
 - **User** has many Lists; identified by the login on the `Remote-User` header
-- **List** belongs to a User and has many Items and Axes, and one source (`source_kind` picks a class from `Sources::ALL`, `source_config` configures it) that produces its Items
+- **List** belongs to a User and has many Items and Axes, and one source (`source_kind` picks a class from `Sources::ALL`; a kind that needs configuring keeps it in its own table keyed by `list_id`) that produces its Items
 - **Item** belongs to a List; items missing from the latest sync are archived (`archived_at`) and hidden everywhere
 - **Axis** belongs to a List, has many Results
 - **Result** belongs to an Axis, a winner (Item), and a loser (Item)
diff --git a/db/migrate/004_add_list_sources.rb b/db/migrate/004_add_list_sources.rb
index b2e305b..58c147b 100644
--- a/db/migrate/004_add_list_sources.rb
+++ b/db/migrate/004_add_list_sources.rb
@@ -3,10 +3,11 @@
 Sequel.migration do
   up do
     # Every list gets its items from exactly one source. Lists before this
-    # were made from the textarea, so they default to the text source.
+    # were made from the textarea, so they default to the text source. A
+    # kind that needs configuring gets its own table keyed by list_id; text
+    # needs none.
     alter_table(:lists) do
       add_column :source_kind, String, null: false, default: "text"
-      add_column :source_config, String, null: false, default: "{}"
       add_column :synced_at, DateTime
     end
 
@@ -35,7 +36,6 @@ Sequel.migration do
 
     alter_table(:lists) do
       drop_column :source_kind
-      drop_column :source_config
       drop_column :synced_at
     end
   end
diff --git a/lib/rank_king/models.rb b/lib/rank_king/models.rb
index 326f2a6..04537a6 100644
--- a/lib/rank_king/models.rb
+++ b/lib/rank_king/models.rb
@@ -21,12 +21,7 @@ module RankKing
     one_to_many :items, class: "RankKing::Item", conditions: { archived_at: nil }
     one_to_many :axes, class: "RankKing::Axis"
 
-    def source_config = JSON.parse(super)
-    def source_config=(value)
-      super(JSON.generate(value))
-    end
-
-    def source = Sources.fetch(source_kind).new(source_config)
+    def source = Sources.fetch(source_kind).for(self)
   end
 
   # @rbs skip
diff --git a/lib/rank_king/sources/text.rb b/lib/rank_king/sources/text.rb
index 8f274a6..aa1fdc8 100644
--- a/lib/rank_king/sources/text.rb
+++ b/lib/rank_king/sources/text.rb
@@ -4,18 +4,15 @@ module RankKing
   module Sources
     # A push source: the snapshot is pasted text, one item per line.
     class Text
-      # @rbs @config: Hash[String, untyped]
-
       #: () -> String
       def self.kind = "text"
 
       #: () -> bool
       def self.push? = true
 
-      #: (?Hash[String, untyped] config) -> void
-      def initialize(config = {})
-        @config = config
-      end
+      # Text has no configuration, so every list's text source is the same.
+      #: (List list) -> Text
+      def self.for(list) = new
 
       #: (untyped snapshot) -> Array[Entry]
       def entries(snapshot)
diff --git a/sig/rank_king/models.rbs b/sig/rank_king/models.rbs
index 8c08277..a136203 100644
--- a/sig/rank_king/models.rbs
+++ b/sig/rank_king/models.rbs
@@ -17,8 +17,6 @@ module RankKing
     def items_dataset: () -> Sequel::Dataset[Item]
     def axes_dataset: () -> Sequel::Dataset[Axis]
     def source_kind: () -> String
-    def source_config: () -> Hash[String, untyped]
-    def source_config=: (Hash[String, untyped] value) -> void
     def synced_at: () -> Time?
     def source: () -> Sources::_Source
   end
diff --git a/sig/rank_king/sources.rbs b/sig/rank_king/sources.rbs
index f389c79..a57e9d2 100644
--- a/sig/rank_king/sources.rbs
+++ b/sig/rank_king/sources.rbs
@@ -22,7 +22,8 @@ module RankKing
     interface _SourceClass
       def kind: () -> String
       def push?: () -> bool
-      def new: (Hash[String, untyped] config) -> _Source
+      # The source for a list, configured from the kind's own table.
+      def for: (List list) -> _Source
     end
   end
 end