Skip to content

Commit af75ae6

Browse files
committed
add nudge for people to share
also run export
1 parent 4dbb90b commit af75ae6

File tree

2 files changed

+129
-42
lines changed

2 files changed

+129
-42
lines changed

docs/llm/dump.txt

Lines changed: 127 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
===============================================================================
22
PROJECT EXPORT
3-
Generated: Fri Jan 16 08:24:55 PM EST 2026
3+
Generated: Fri Jan 16 08:42:23 PM EST 2026
44
Project Path: /home/kushal/src/dotnet/MyBlog
55
===============================================================================
66

@@ -144,6 +144,8 @@ DIRECTORY STRUCTURE:
144144
│   │   │   ├── App.razor
145145
│   │   │   ├── _Imports.razor
146146
│   │   │   └── Routes.razor
147+
│   │   ├── Hubs
148+
│   │   │   └── ReaderHub.cs
147149
│   │   ├── Middleware
148150
│   │   │   └── LoginRateLimitMiddleware.cs
149151
│   │   ├── wwwroot
@@ -4097,8 +4099,8 @@ MODIFIED: 2026-01-01 22:21:17
40974099

40984100
================================================================================
40994101
FILE: src/Directory.Packages.props
4100-
SIZE: 1.36 KB
4101-
MODIFIED: 2026-01-13 21:25:12
4102+
SIZE: 1.45 KB
4103+
MODIFIED: 2026-01-16 20:29:40
41024104
================================================================================
41034105

41044106
<Project>
@@ -4108,6 +4110,7 @@ MODIFIED: 2026-01-13 21:25:12
41084110
</PropertyGroup>
41094111
<ItemGroup>
41104112
<!-- Core Framework (.NET 10) -->
4113+
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.2" />
41114114
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
41124115
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.1" />
41134116
<PackageVersion Include="Microsoft.AspNetCore.Identity" Version="2.3.9" />
@@ -10004,25 +10007,36 @@ public interface IPostRepository
1000410007

1000510008
================================================================================
1000610009
FILE: src/MyBlog.Core/Interfaces/IReaderTrackingService.cs
10007-
SIZE: .39 KB
10008-
MODIFIED: 2026-01-13 08:55:34
10010+
SIZE: .90 KB
10011+
MODIFIED: 2026-01-16 20:30:07
1000910012
================================================================================
1001010013

1001110014
namespace MyBlog.Core.Interfaces;
1001210015

1001310016
public interface IReaderTrackingService
1001410017
{
10015-
// Called when a user opens a post
10016-
void JoinPost(string slug);
10018+
/// <summary>
10019+
/// Registers a connection viewing a specific post.
10020+
/// </summary>
10021+
/// <returns>The new reader count for this slug.</returns>
10022+
int JoinPost(string slug, string connectionId);
1001710023

10018-
// Called when a user leaves (closes tab/navigates away)
10019-
void LeavePost(string slug);
10024+
/// <summary>
10025+
/// Unregisters a connection from a specific post.
10026+
/// </summary>
10027+
/// <returns>The new reader count for this slug.</returns>
10028+
int LeavePost(string slug, string connectionId);
1002010029

10021-
// Gets the current count
10022-
int GetReaderCount(string slug);
10030+
/// <summary>
10031+
/// Handles a disconnection event (e.g. tab closed) and determines which slug was being viewed.
10032+
/// </summary>
10033+
/// <returns>A tuple containing the Slug that was left (if any) and the new count.</returns>
10034+
(string? Slug, int NewCount) Disconnect(string connectionId);
1002310035

10024-
// Event that fires when the count changes
10025-
event Action<string, int>? OnCountChanged;
10036+
/// <summary>
10037+
/// Gets the current count for a specific post.
10038+
/// </summary>
10039+
int GetReaderCount(string slug);
1002610040
}
1002710041

1002810042

@@ -11374,8 +11388,8 @@ public sealed class PasswordService : IPasswordService
1137411388

1137511389
================================================================================
1137611390
FILE: src/MyBlog.Infrastructure/Services/ReaderTrackingService.cs
11377-
SIZE: 1.19 KB
11378-
MODIFIED: 2026-01-16 19:03:44
11391+
SIZE: 1.57 KB
11392+
MODIFIED: 2026-01-16 20:30:23
1137911393
================================================================================
1138011394

1138111395
using System.Collections.Concurrent;
@@ -11385,38 +11399,46 @@ namespace MyBlog.Infrastructure.Services;
1138511399

1138611400
public class ReaderTrackingService : IReaderTrackingService
1138711401
{
11388-
// Thread-safe dictionary to store counts: Slug -> Count
11389-
private readonly ConcurrentDictionary<string, int> _activeReaders = new();
11402+
// Maps Slug -> Count of active readers
11403+
private readonly ConcurrentDictionary<string, int> _slugCounts = new();
1139011404

11391-
public event Action<string, int>? OnCountChanged;
11405+
// Maps ConnectionId -> Slug (Reverse lookup to handle disconnects)
11406+
private readonly ConcurrentDictionary<string, string> _connectionMap = new();
1139211407

11393-
public void JoinPost(string slug)
11408+
public int JoinPost(string slug, string connectionId)
1139411409
{
11395-
// Atomically increment the count
11396-
var newCount = _activeReaders.AddOrUpdate(slug, 1, (_, count) => count + 1);
11410+
// Map the connection to the slug
11411+
_connectionMap.AddOrUpdate(connectionId, slug, (_, _) => slug);
1139711412

11398-
// Notify subscribers
11399-
OnCountChanged?.Invoke(slug, newCount);
11413+
// Increment the count for this slug
11414+
return _slugCounts.AddOrUpdate(slug, 1, (_, count) => count + 1);
1140011415
}
1140111416

11402-
public void LeavePost(string slug)
11417+
public int LeavePost(string slug, string connectionId)
1140311418
{
11404-
// Atomically decrement the count
11405-
var newCount = _activeReaders.AddOrUpdate(slug, 0, (_, count) => count > 0 ? count - 1 : 0);
11419+
// Remove the connection mapping
11420+
_connectionMap.TryRemove(connectionId, out _);
11421+
11422+
// Decrement the count
11423+
return _slugCounts.AddOrUpdate(slug, 0, (_, count) => count > 0 ? count - 1 : 0);
11424+
}
1140611425

11407-
// If count is 0, we could remove the key, but keeping it is harmless for small blogs
11408-
if (newCount == 0)
11426+
public (string? Slug, int NewCount) Disconnect(string connectionId)
11427+
{
11428+
// Find which slug this connection was watching
11429+
if (_connectionMap.TryRemove(connectionId, out var slug))
1140911430
{
11410-
_activeReaders.TryRemove(slug, out _);
11431+
// Decrement that slug's count
11432+
var newCount = _slugCounts.AddOrUpdate(slug, 0, (_, count) => count > 0 ? count - 1 : 0);
11433+
return (slug, newCount);
1141111434
}
1141211435

11413-
// Notify subscribers
11414-
OnCountChanged?.Invoke(slug, newCount);
11436+
return (null, 0);
1141511437
}
1141611438

1141711439
public int GetReaderCount(string slug)
1141811440
{
11419-
return _activeReaders.TryGetValue(slug, out var count) ? count : 0;
11441+
return _slugCounts.TryGetValue(slug, out var count) ? count : 0;
1142011442
}
1142111443
}
1142211444

@@ -14136,6 +14158,62 @@ MODIFIED: 2026-01-13 21:06:07
1413614158
}
1413714159

1413814160

14161+
================================================================================
14162+
FILE: src/MyBlog.Web/Hubs/ReaderHub.cs
14163+
SIZE: 1.39 KB
14164+
MODIFIED: 2026-01-16 20:30:48
14165+
================================================================================
14166+
14167+
using Microsoft.AspNetCore.SignalR;
14168+
using MyBlog.Core.Interfaces;
14169+
14170+
namespace MyBlog.Web.Hubs;
14171+
14172+
public class ReaderHub : Hub
14173+
{
14174+
private readonly IReaderTrackingService _trackingService;
14175+
14176+
public ReaderHub(IReaderTrackingService trackingService)
14177+
{
14178+
_trackingService = trackingService;
14179+
}
14180+
14181+
public async Task JoinPage(string slug)
14182+
{
14183+
// Add this connection to the SignalR group for this slug
14184+
await Groups.AddToGroupAsync(Context.ConnectionId, slug);
14185+
14186+
// Update state
14187+
var newCount = _trackingService.JoinPost(slug, Context.ConnectionId);
14188+
14189+
// Broadcast new count to everyone in this group
14190+
await Clients.Group(slug).SendAsync("UpdateCount", newCount);
14191+
}
14192+
14193+
public async Task LeavePage(string slug)
14194+
{
14195+
await Groups.RemoveFromGroupAsync(Context.ConnectionId, slug);
14196+
14197+
var newCount = _trackingService.LeavePost(slug, Context.ConnectionId);
14198+
14199+
await Clients.Group(slug).SendAsync("UpdateCount", newCount);
14200+
}
14201+
14202+
public override async Task OnDisconnectedAsync(Exception? exception)
14203+
{
14204+
// Handle abrupt disconnects (tab closed, network lost)
14205+
var (slug, newCount) = _trackingService.Disconnect(Context.ConnectionId);
14206+
14207+
if (!string.IsNullOrEmpty(slug))
14208+
{
14209+
await Clients.Group(slug).SendAsync("UpdateCount", newCount);
14210+
}
14211+
14212+
await base.OnDisconnectedAsync(exception);
14213+
}
14214+
}
14215+
14216+
1413914217
================================================================================
1414014218
FILE: src/MyBlog.Web/Middleware/LoginRateLimitMiddleware.cs
1414114219
SIZE: 5.64 KB
@@ -14337,8 +14415,8 @@ public static class LoginRateLimitMiddlewareExtensions
1433714415

1433814416
================================================================================
1433914417
FILE: src/MyBlog.Web/MyBlog.Web.csproj
14340-
SIZE: .55 KB
14341-
MODIFIED: 2026-01-13 21:22:06
14418+
SIZE: .62 KB
14419+
MODIFIED: 2026-01-16 20:29:40
1434214420
================================================================================
1434314421

1434414422
<Project Sdk="Microsoft.NET.Sdk.Web">
@@ -14347,6 +14425,7 @@ MODIFIED: 2026-01-13 21:22:06
1434714425
</PropertyGroup>
1434814426

1434914427
<ItemGroup>
14428+
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" />
1435014429
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
1435114430
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
1435214431
<PackageReference Include="OpenTelemetry.Exporter.Console" />
@@ -14360,8 +14439,8 @@ MODIFIED: 2026-01-13 21:22:06
1436014439

1436114440
================================================================================
1436214441
FILE: src/MyBlog.Web/Program.cs
14363-
SIZE: 3.73 KB
14364-
MODIFIED: 2026-01-14 19:57:11
14442+
SIZE: 3.84 KB
14443+
MODIFIED: 2026-01-16 20:31:17
1436514444
================================================================================
1436614445

1436714446
using Microsoft.AspNetCore.Authentication;
@@ -14374,6 +14453,7 @@ using MyBlog.Infrastructure.Data;
1437414453
using MyBlog.Infrastructure.Services;
1437514454
using MyBlog.Infrastructure.Telemetry;
1437614455
using MyBlog.Web.Components;
14456+
using MyBlog.Web.Hubs; // ADD THIS
1437714457
using MyBlog.Web.Middleware;
1437814458
using OpenTelemetry;
1437914459
using OpenTelemetry.Logs;
@@ -14387,6 +14467,9 @@ var builder = WebApplication.CreateBuilder(args);
1438714467
builder.Services.AddRazorComponents()
1438814468
.AddInteractiveServerComponents();
1438914469

14470+
// ADD THIS: Register SignalR
14471+
builder.Services.AddSignalR();
14472+
1439014473
builder.Services.AddInfrastructure(builder.Configuration);
1439114474

1439214475
// Register TelemetryCleanupService as a hosted service
@@ -14400,7 +14483,7 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
1440014483
options.Cookie.Name = AppConstants.AuthCookieName;
1440114484
options.LoginPath = "/login";
1440214485
options.LogoutPath = "/logout";
14403-
options.AccessDeniedPath = "/access-denied"; // FIX: Added explicit Access Denied path
14486+
options.AccessDeniedPath = "/access-denied";
1440414487
options.ExpireTimeSpan = TimeSpan.FromMinutes(sessionTimeout);
1440514488
options.SlidingExpiration = true;
1440614489
options.Cookie.HttpOnly = true;
@@ -14417,6 +14500,7 @@ builder.Services.AddAntiforgery();
1441714500
// OpenTelemetry configuration
1441814501
var serviceName = "MyBlog.Web";
1441914502
var serviceVersion = typeof(Program).Assembly.GetName().Version?.ToString() ?? "1.0.0";
14503+
1442014504
builder.Services.AddOpenTelemetry()
1442114505
.ConfigureResource(resource => resource
1442214506
.AddService(serviceName: serviceName, serviceVersion: serviceVersion))
@@ -14477,6 +14561,9 @@ app.MapPost("/logout", async (HttpContext context) =>
1447714561
return Results.Redirect("/");
1447814562
}).RequireAuthorization();
1447914563

14564+
// ADD THIS: Map the Hub
14565+
app.MapHub<ReaderHub>("/readerHub");
14566+
1448014567
app.MapRazorComponents<App>()
1448114568
.AddInteractiveServerRenderMode();
1448214569

@@ -16292,9 +16379,9 @@ echo "=============================================="
1629216379

1629316380

1629416381
===============================================================================
16295-
EXPORT COMPLETED: Fri Jan 16 08:24:56 PM EST 2026
16296-
Total Files Found: 85
16297-
Files Exported: 85
16382+
EXPORT COMPLETED: Fri Jan 16 08:42:24 PM EST 2026
16383+
Total Files Found: 86
16384+
Files Exported: 86
1629816385
Files Skipped: 0 (binary or large files)
1629916386
Output File: /home/kushal/src/dotnet/MyBlog/docs/llm/dump.txt
1630016387
===============================================================================

src/MyBlog.Web/Components/Shared/ReaderBadge.razor

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
@inject NavigationManager Navigation
33
@implements IAsyncDisposable
44

5-
<div class="reader-badge" title="Active readers on this post">
5+
<div class="reader-badge" title="Active readers on this post. Share this post to increase this number!">
66
<span class="reader-dot">●</span>
7-
<span>@(_count) reading now</span>
7+
<span>@_count @(_count == 1 ? "person" : "people") reading now.</span>
88
</div>
99

1010
@code {

0 commit comments

Comments
 (0)