From db40411fa372e81be9756ffed9a33b277b99c0bb Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Fri, 21 Nov 2025 21:00:11 -0500 Subject: feat: implement t-rWbMpxaBk --- .merge_file_Akj4Wc.lock | 0 .merge_file_LGcQlo.lock | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 .merge_file_Akj4Wc.lock create mode 100644 .merge_file_LGcQlo.lock diff --git a/.merge_file_Akj4Wc.lock b/.merge_file_Akj4Wc.lock new file mode 100644 index 0000000..e69de29 diff --git a/.merge_file_LGcQlo.lock b/.merge_file_LGcQlo.lock new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From 829f175cb2ad376f800df5459d57ada7e346c220 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Fri, 21 Nov 2025 22:44:51 -0500 Subject: task: sync database --- Biz/PodcastItLater/Admin.py | 53 ++++++++++++ Biz/PodcastItLater/Core.py | 62 ++++++++++++++ Biz/PodcastItLater/UI.py | 204 ++++++++++++++++++++++---------------------- Biz/PodcastItLater/Web.py | 31 +++++++ 4 files changed, 247 insertions(+), 103 deletions(-) diff --git a/Biz/PodcastItLater/Admin.py b/Biz/PodcastItLater/Admin.py index 10a8e58..f1dc3ab 100644 --- a/Biz/PodcastItLater/Admin.py +++ b/Biz/PodcastItLater/Admin.py @@ -156,6 +156,59 @@ class MetricsDashboard(Component[AnyChildren, MetricsAttrs]): return UI.PageLayout( html.div( + html.h2( + html.i(classes=["bi", "bi-people", "me-2"]), + "Growth & Usage", + classes=["mb-4"], + ), + # Growth & Usage cards + html.div( + html.div( + html.div( + MetricCard( + title="Total Users", + value=metrics.get("total_users", 0), + icon="bi-people", + ), + classes=["card", "shadow-sm"], + ), + classes=["col-md-3"], + ), + html.div( + html.div( + MetricCard( + title="Active Subs", + value=metrics.get("active_subscriptions", 0), + icon="bi-credit-card", + ), + classes=["card", "shadow-sm"], + ), + classes=["col-md-3"], + ), + html.div( + html.div( + MetricCard( + title="Submissions (24h)", + value=metrics.get("submissions_24h", 0), + icon="bi-activity", + ), + classes=["card", "shadow-sm"], + ), + classes=["col-md-3"], + ), + html.div( + html.div( + MetricCard( + title="Submissions (7d)", + value=metrics.get("submissions_7d", 0), + icon="bi-calendar-week", + ), + classes=["card", "shadow-sm"], + ), + classes=["col-md-3"], + ), + classes=["row", "g-3", "mb-5"], + ), html.h2( html.i(classes=["bi", "bi-graph-up", "me-2"]), "Episode Metrics", diff --git a/Biz/PodcastItLater/Core.py b/Biz/PodcastItLater/Core.py index 8d31956..4c09a23 100644 --- a/Biz/PodcastItLater/Core.py +++ b/Biz/PodcastItLater/Core.py @@ -1100,6 +1100,10 @@ class Database: # noqa: PLR0904 - most_played: List of top 10 most played episodes - most_downloaded: List of top 10 most downloaded episodes - most_added: List of top 10 most added episodes + - total_users: Total number of users + - active_subscriptions: Number of active subscriptions + - submissions_24h: Submissions in last 24 hours + - submissions_7d: Submissions in last 7 days """ with Database.get_connection() as conn: cursor = conn.cursor() @@ -1169,6 +1173,29 @@ class Database: # noqa: PLR0904 ) most_added = [dict(row) for row in cursor.fetchall()] + # Get user metrics + cursor.execute("SELECT COUNT(*) as count FROM users") + total_users = cursor.fetchone()["count"] + + cursor.execute( + "SELECT COUNT(*) as count FROM users " + "WHERE subscription_status = 'active'", + ) + active_subscriptions = cursor.fetchone()["count"] + + # Get recent submission metrics + cursor.execute( + "SELECT COUNT(*) as count FROM queue " + "WHERE created_at >= datetime('now', '-1 day')", + ) + submissions_24h = cursor.fetchone()["count"] + + cursor.execute( + "SELECT COUNT(*) as count FROM queue " + "WHERE created_at >= datetime('now', '-7 days')", + ) + submissions_7d = cursor.fetchone()["count"] + return { "total_episodes": total_episodes, "total_plays": total_plays, @@ -1177,6 +1204,10 @@ class Database: # noqa: PLR0904 "most_played": most_played, "most_downloaded": most_downloaded, "most_added": most_added, + "total_users": total_users, + "active_subscriptions": active_subscriptions, + "submissions_24h": submissions_24h, + "submissions_7d": submissions_7d, } @staticmethod @@ -1477,6 +1508,37 @@ class TestDatabase(Test.TestCase): # Test completed successfully - migration worked self.assertIsNotNone(conn) + def test_get_metrics_summary_extended(self) -> None: + """Verify extended metrics summary.""" + # Create some data + user_id, _ = Database.create_user("test@example.com") + Database.create_episode( + "Test Article", + "url", + 100, + 1000, + user_id, + ) + + # Create a queue item + Database.add_to_queue( + "https://example.com", + "test@example.com", + user_id, + ) + + metrics = Database.get_metrics_summary() + + self.assertIn("total_users", metrics) + self.assertIn("active_subscriptions", metrics) + self.assertIn("submissions_24h", metrics) + self.assertIn("submissions_7d", metrics) + + self.assertEqual(metrics["total_users"], 1) + self.assertEqual(metrics["submissions_24h"], 1) + self.assertEqual(metrics["submissions_7d"], 1) + + class TestUserManagement(Test.TestCase): """Test user management functionality.""" diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index 27f5fff..a83ad32 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -422,130 +422,128 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): current_tier = user.get("plan_tier", "free") if user else "free" return PageLayout( - user=user, - current_page="pricing", - page_title="Pricing - PodcastItLater", - error=None, - meta_tags=[], - children=[ + html.div( + html.h2("Simple Pricing", classes=["text-center", "mb-5"]), html.div( - html.h2("Simple Pricing", classes=["text-center", "mb-5"]), + # Free Tier + html.div( + html.div( + html.div( + html.h3("Free", classes=["card-title"]), + html.h4( + "$0", + classes=[ + "card-subtitle", + "mb-3", + "text-muted", + ], + ), + html.p( + "10 articles total", + classes=["card-text"], + ), + html.ul( + html.li("Convert 10 articles"), + html.li("Basic features"), + classes=["list-unstyled", "mb-4"], + ), + html.button( + "Current Plan", + classes=[ + "btn", + "btn-outline-primary", + "w-100", + ], + disabled=True, + ) + if current_tier == "free" + else html.div(), + classes=["card-body"], + ), + classes=["card", "mb-4", "shadow-sm", "h-100"], + ), + classes=["col-md-6"], + ), + # Paid Tier html.div( - # Free Tier html.div( html.div( - html.div( - html.h3("Free", classes=["card-title"]), - html.h4( - "$0", + html.h3( + "Unlimited", + classes=["card-title"], + ), + html.h4( + "$12/mo", + classes=[ + "card-subtitle", + "mb-3", + "text-muted", + ], + ), + html.p( + "Unlimited articles", + classes=["card-text"], + ), + html.ul( + html.li("Unlimited conversions"), + html.li("Priority processing"), + html.li("Support independent software"), + classes=["list-unstyled", "mb-4"], + ), + html.form( + html.button( + "Upgrade Now", + type="submit", classes=[ - "card-subtitle", - "mb-3", - "text-muted", + "btn", + "btn-primary", + "w-100", ], ), - html.p( - "10 articles total", - classes=["card-text"], - ), - html.ul( - html.li("Convert 10 articles"), - html.li("Basic features"), - classes=["list-unstyled", "mb-4"], - ), + action="/upgrade", + method="post", + ) + if user and current_tier == "free" + else ( html.button( "Current Plan", classes=[ "btn", - "btn-outline-primary", + "btn-success", "w-100", ], disabled=True, ) - if current_tier == "free" - else html.div(), - classes=["card-body"], - ), - classes=["card", "mb-4", "shadow-sm", "h-100"], - ), - classes=["col-md-6"], - ), - # Paid Tier - html.div( - html.div( - html.div( - html.h3( - "Unlimited", - classes=["card-title"], - ), - html.h4( - "$12/mo", + if current_tier == "paid" + else html.a( + "Login to Upgrade", + href="/", classes=[ - "card-subtitle", - "mb-3", - "text-muted", + "btn", + "btn-primary", + "w-100", ], - ), - html.p( - "Unlimited articles", - classes=["card-text"], - ), - html.ul( - html.li("Unlimited conversions"), - html.li("Priority processing"), - html.li("Support independent software"), - classes=["list-unstyled", "mb-4"], - ), - html.form( - html.button( - "Upgrade Now", - type="submit", - classes=[ - "btn", - "btn-primary", - "w-100", - ], - ), - action="/upgrade", - method="POST", ) - if user and current_tier == "free" - else ( - html.button( - "Current Plan", - classes=[ - "btn", - "btn-success", - "w-100", - ], - disabled=True, - ) - if user and current_tier == "paid" - else html.a( - "Login to Upgrade", - href="/", - classes=[ - "btn", - "btn-primary", - "w-100", - ], - ) - ), - classes=["card-body"], ), - classes=[ - "card", - "mb-4", - "shadow-sm", - "border-primary", - "h-100", - ], + classes=["card-body"], ), - classes=["col-md-6"], + classes=[ + "card", + "mb-4", + "shadow-sm", + "border-primary", + "h-100", + ], ), - classes=["row"], + classes=["col-md-6"], ), - classes=["container", "py-3"], + classes=["row"], ), - ], + classes=["container", "py-3"], + ), + user=user, + current_page="pricing", + page_title="Pricing - PodcastItLater", + error=None, + meta_tags=[], ) diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py index 7e8e969..368db98 100644 --- a/Biz/PodcastItLater/Web.py +++ b/Biz/PodcastItLater/Web.py @@ -2398,6 +2398,37 @@ class TestMetricsDashboard(BaseWebTest): self.assertIn("Episode Metrics", response.text) self.assertIn("Total Episodes", response.text) self.assertIn("Total Plays", response.text) + + def test_growth_metrics_display(self) -> None: + """Verify growth and usage metrics are displayed.""" + # Create an active subscriber + user2_id, _ = Core.Database.create_user("active@example.com") + Core.Database.update_user_subscription( + user2_id, + subscription_id="sub_test", + status="active", + period_start=datetime.now(timezone.utc), + period_end=datetime.now(timezone.utc), + tier="paid", + cancel_at_period_end=False, + ) + + # Create a queue item + Core.Database.add_to_queue( + "https://example.com/new", + "active@example.com", + user2_id, + ) + + # Get metrics page + response = self.client.get("/admin/metrics") + + self.assertEqual(response.status_code, 200) + self.assertIn("Growth & Usage", response.text) + self.assertIn("Total Users", response.text) + self.assertIn("Active Subs", response.text) + self.assertIn("Submissions (24h)", response.text) + self.assertIn("Total Downloads", response.text) self.assertIn("Total Adds", response.text) -- cgit v1.2.3 From e5408a71ac1b4997b512ecba6e0426a5018f974f Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Fri, 21 Nov 2025 22:54:20 -0500 Subject: task: claim t-rWbMpxaBk --- .merge_file_xbLQDn.lock | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .merge_file_xbLQDn.lock diff --git a/.merge_file_xbLQDn.lock b/.merge_file_xbLQDn.lock new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From 9e6ffac51e82b83afad3d61e8de103d351a7b06f Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 04:47:27 -0500 Subject: task: claim t-rWbMpxaBk --- Biz/PodcastItLater/Core.py | 2 +- Biz/PodcastItLater/Web.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Biz/PodcastItLater/Core.py b/Biz/PodcastItLater/Core.py index 4c09a23..fc299cc 100644 --- a/Biz/PodcastItLater/Core.py +++ b/Biz/PodcastItLater/Core.py @@ -388,7 +388,7 @@ class Database: # noqa: PLR0904 cursor.execute( """ SELECT id, title, audio_url, duration, created_at, - content_length, author, original_url, user_id + content_length, author, original_url, user_id, is_public FROM episodes WHERE id = ? """, diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py index 368db98..e41b980 100644 --- a/Biz/PodcastItLater/Web.py +++ b/Biz/PodcastItLater/Web.py @@ -1811,7 +1811,7 @@ def add_episode_to_feed(request: Request, episode_id: int) -> Response: Core.Database.add_episode_to_user(user_id, episode_id) # Track the "added" event - Core.Database.track_episode_metric(episode_id, "added", user_id) + Core.Database.track_episode_event(episode_id, "added", user_id) # Reload the current page to show updated button state # Check referer to determine where to redirect @@ -1842,7 +1842,7 @@ def track_episode( user_id = request.session.get("user_id") # Track the event - Core.Database.track_episode_metric(episode_id, event_type, user_id) + Core.Database.track_episode_event(episode_id, event_type, user_id) return Response("", status_code=200) @@ -2359,7 +2359,7 @@ class TestMetricsDashboard(BaseWebTest): self.client.post("/login", data={"email": "user@example.com"}) # Try to access metrics - response = self.client.get("/admin/metrics") + response = self.client.get("/admin/metrics", follow_redirects=False) # Should redirect self.assertEqual(response.status_code, 302) @@ -2369,7 +2369,7 @@ class TestMetricsDashboard(BaseWebTest): """Verify unauthenticated users are redirected.""" self.client.get("/logout") - response = self.client.get("/admin/metrics") + response = self.client.get("/admin/metrics", follow_redirects=False) self.assertEqual(response.status_code, 302) self.assertEqual(response.headers["Location"], "/") @@ -2386,10 +2386,10 @@ class TestMetricsDashboard(BaseWebTest): Core.Database.add_episode_to_user(self.user_id, episode_id) # Track some events - Core.Database.track_episode_metric(episode_id, "played") - Core.Database.track_episode_metric(episode_id, "played") - Core.Database.track_episode_metric(episode_id, "downloaded") - Core.Database.track_episode_metric(episode_id, "added", self.user_id) + Core.Database.track_episode_event(episode_id, "played") + Core.Database.track_episode_event(episode_id, "played") + Core.Database.track_episode_event(episode_id, "downloaded") + Core.Database.track_episode_event(episode_id, "added", self.user_id) # Get metrics page response = self.client.get("/admin/metrics") @@ -2454,13 +2454,13 @@ class TestMetricsDashboard(BaseWebTest): # Track events - more for episode1 for _ in range(5): - Core.Database.track_episode_metric(episode1, "played") + Core.Database.track_episode_event(episode1, "played") for _ in range(2): - Core.Database.track_episode_metric(episode2, "played") + Core.Database.track_episode_event(episode2, "played") for _ in range(3): - Core.Database.track_episode_metric(episode1, "downloaded") - Core.Database.track_episode_metric(episode2, "downloaded") + Core.Database.track_episode_event(episode1, "downloaded") + Core.Database.track_episode_event(episode2, "downloaded") # Get metrics page response = self.client.get("/admin/metrics") -- cgit v1.2.3 From 1fa573be7dde6f5a0c1719b0a3bc386222b96c0a Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 04:57:20 -0500 Subject: task: claim t-rWbMpxaBk --- Biz/PodcastItLater/UI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index a83ad32..d315249 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -11,7 +11,7 @@ import typing from ludic.attrs import Attrs from ludic.components import Component from ludic.types import AnyChildren -from typing import override +from typing import Any, cast, override def format_duration(seconds: int | None) -> str: @@ -501,7 +501,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): ], ), action="/upgrade", - method="post", + method="get", ) if user and current_tier == "free" else ( -- cgit v1.2.3 From 6042e2145478dd966eea697a2286ff3f232acf95 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:08:07 -0500 Subject: task: claim t-rWbMpxaBk --- Biz/PodcastItLater/TestMetricsView.py | 101 ++++++++++++++++++++++++++++++++++ Biz/PodcastItLater/UI.py | 4 +- 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 Biz/PodcastItLater/TestMetricsView.py diff --git a/Biz/PodcastItLater/TestMetricsView.py b/Biz/PodcastItLater/TestMetricsView.py new file mode 100644 index 0000000..9ca30c2 --- /dev/null +++ b/Biz/PodcastItLater/TestMetricsView.py @@ -0,0 +1,101 @@ +# : out podcastitlater-test-metrics +# : dep pytest +# : dep starlette +# : dep httpx +# : dep ludic +# : dep feedgen +# : dep itsdangerous +# : dep uvicorn +# : dep stripe +# : dep sqids + +import Biz.PodcastItLater.Core as Core +import Biz.PodcastItLater.Web as Web +import Omni.Test as Test +from starlette.testclient import TestClient + +class BaseWebTest(Test.TestCase): + def setUp(self) -> None: + Core.Database.init_db() + self.client = TestClient(Web.app) + + @staticmethod + def tearDown() -> None: + Core.Database.teardown() + +class TestMetricsView(BaseWebTest): + def test_admin_metrics_view_access(self): + """Admin user should be able to access metrics view.""" + # Create admin user + admin_id, _ = Core.Database.create_user("ben@bensima.com") + self.client.post("/login", data={"email": "ben@bensima.com"}) + + response = self.client.get("/admin/metrics") + self.assertEqual(response.status_code, 200) + self.assertIn("Growth & Usage", response.text) + self.assertIn("Total Users", response.text) + + def test_admin_metrics_data(self): + """Metrics view should show correct data.""" + # Create admin user + admin_id, _ = Core.Database.create_user("ben@bensima.com") + self.client.post("/login", data={"email": "ben@bensima.com"}) + + # Create some data + # 1. Users + Core.Database.create_user("user1@example.com") + user2_id, _ = Core.Database.create_user("user2@example.com") + + # 2. Subscriptions (simulate by setting subscription_status) + with Core.Database.get_connection() as conn: + conn.execute("UPDATE users SET subscription_status = 'active' WHERE id = ?", (user2_id,)) + conn.commit() + + # 3. Submissions + Core.Database.add_to_queue("http://example.com/1", "user1@example.com", admin_id) + + # Get metrics page + response = self.client.get("/admin/metrics") + self.assertEqual(response.status_code, 200) + + # Check labels + self.assertIn("Total Users", response.text) + self.assertIn("Active Subs", response.text) + self.assertIn("Submissions (24h)", response.text) + + # Check values (metrics dict is passed to template, we check rendered HTML) + # Total users: 3 (admin + user1 + user2) + # Active subs: 1 (user2) + # Submissions 24h: 1 + + # Check for values in HTML + # Note: This is a bit brittle, but effective for quick verification + self.assertIn('

3

', response.text) + self.assertIn('

1

', response.text) + + def test_non_admin_access_denied(self): + """Non-admin users should be denied access.""" + # Create regular user + Core.Database.create_user("regular@example.com") + self.client.post("/login", data={"email": "regular@example.com"}) + + response = self.client.get("/admin/metrics") + # Should redirect to /?error=forbidden + self.assertEqual(response.status_code, 302) + self.assertIn("error=forbidden", response.headers["Location"]) + + def test_anonymous_access_redirect(self): + """Anonymous users should be redirected to login.""" + response = self.client.get("/admin/metrics") + self.assertEqual(response.status_code, 302) + self.assertEqual(response.headers["Location"], "/") + +def test() -> None: + """Run the tests.""" + Test.run( + Web.area, + [TestMetricsView], + ) + +if __name__ == "__main__": + test() diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index d315249..5102652 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -422,7 +422,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): current_tier = user.get("plan_tier", "free") if user else "free" return PageLayout( - html.div( + cast(Any, html.div( html.h2("Simple Pricing", classes=["text-center", "mb-5"]), html.div( # Free Tier @@ -501,7 +501,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): ], ), action="/upgrade", - method="get", + method=cast(Any, "get"), ) if user and current_tier == "free" else ( -- cgit v1.2.3 From 0ad15e7f3bb47e66314aa49c8ca2c7eef84c0513 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:17:01 -0500 Subject: task: claim t-rWbMpxaBk --- Biz/PodcastItLater/UI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index 5102652..c992c72 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -422,7 +422,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): current_tier = user.get("plan_tier", "free") if user else "free" return PageLayout( - cast(Any, html.div( + html.div( # type: ignore[arg-type] html.h2("Simple Pricing", classes=["text-center", "mb-5"]), html.div( # Free Tier @@ -501,7 +501,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): ], ), action="/upgrade", - method=cast(Any, "get"), + method="get", # type: ignore[arg-type] ) if user and current_tier == "free" else ( -- cgit v1.2.3 From 240aabb20f2d8ac5d7f445c2d53afef3426b9d30 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:27:01 -0500 Subject: Fix mypy errors in Biz/PodcastItLater/UI.py Amp-Thread-ID: https://ampcode.com/threads/T-9e42644b-2a21-40e9-923c-e63f9026b0a6 Co-authored-by: Amp --- Biz/PodcastItLater/UI.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index c992c72..e212f83 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -422,7 +422,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): current_tier = user.get("plan_tier", "free") if user else "free" return PageLayout( - html.div( # type: ignore[arg-type] + cast(Any, html.div( # type: ignore[arg-type] html.h2("Simple Pricing", classes=["text-center", "mb-5"]), html.div( # Free Tier @@ -501,7 +501,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): ], ), action="/upgrade", - method="get", # type: ignore[arg-type] + attrs={"method": "get"}, # type: ignore[arg-type] ) if user and current_tier == "free" else ( -- cgit v1.2.3 From 001821558bdee18d50a189cc21b12a2d5f2d8fea Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:31:38 -0500 Subject: Add type ignore to html.form in UI.py --- Biz/PodcastItLater/UI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index e212f83..1fd3276 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -490,7 +490,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): html.li("Support independent software"), classes=["list-unstyled", "mb-4"], ), - html.form( + html.form( # type: ignore[arg-type] html.button( "Upgrade Now", type="submit", -- cgit v1.2.3 From 47e522081531a73c9f4f09e00f08add8ba427653 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:35:45 -0500 Subject: Fix syntax error in UI.py (missing closing parenthesis) --- Biz/PodcastItLater/UI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index 1fd3276..94c5b81 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -540,7 +540,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): classes=["row"], ), classes=["container", "py-3"], - ), + )), user=user, current_page="pricing", page_title="Pricing - PodcastItLater", -- cgit v1.2.3 From df175988db8611779c7e7ed7fcdae5bd4edd57af Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:38:55 -0500 Subject: Fix form method to POST for upgrade action --- Biz/PodcastItLater/UI.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py index 94c5b81..226a7af 100644 --- a/Biz/PodcastItLater/UI.py +++ b/Biz/PodcastItLater/UI.py @@ -501,7 +501,7 @@ class PricingPage(Component[AnyChildren, PricingPageAttrs]): ], ), action="/upgrade", - attrs={"method": "get"}, # type: ignore[arg-type] + attrs={"method": "post"}, # type: ignore[arg-type] ) if user and current_tier == "free" else ( -- cgit v1.2.3 From 0eafc36823e72ce70083cf55be8533087c0b1139 Mon Sep 17 00:00:00 2001 From: Omni Worker Date: Sat, 22 Nov 2025 05:39:20 -0500 Subject: feat: implement t-rWbMpxaBk --- .tasks/tasks.jsonl | 1 + 1 file changed, 1 insertion(+) diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl index 413d2b5..41789b7 100644 --- a/.tasks/tasks.jsonl +++ b/.tasks/tasks.jsonl @@ -175,3 +175,4 @@ {"taskCreatedAt":"2025-11-22T09:50:59.154884329Z","taskDependencies":[],"taskDescription":"1. Add Thread ID to the status bar (requires log parsing later, but add field now). 2. Make the status layout responsive or vertical (4 lines) to fit on small screens (iPhone). 3. Reserve more lines in init.","taskId":"t-rWciWJYsi","taskNamespace":"Omni/Agent/Log.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Improve Agent Status UI for mobile & debugging","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T09:52:36.176467065Z"} {"taskCreatedAt":"2025-11-22T10:09:23.249166289Z","taskDependencies":[],"taskDescription":null,"taskId":"t-rWck9sDOA","taskNamespace":"Omni/Agent.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Split Thread and Credits in Worker status bar","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T10:10:17.800528662Z"} {"taskCreatedAt":"2025-11-22T10:12:35.129294132Z","taskDependencies":[{"depId":"t-rWck9sDOA","depType":"DiscoveredFrom"}],"taskDescription":null,"taskId":"t-rWckmrKBm","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Fix Worker status bar activity not updating","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T10:14:43.612634394Z"} +{"taskCreatedAt":"2025-11-22T10:39:11.364170862Z","taskDependencies":[{"depId":"t-rWbMpxaBk","depType":"DiscoveredFrom"}],"taskDescription":null,"taskId":"t-rWcm6todb","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Open","taskTitle":"Fix failing tests in Biz/PodcastItLater/Web.py (UsageLimits and EpisodeDetail)","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T10:39:11.364170862Z"} -- cgit v1.2.3