StyloIssues 0.0.2

dotnet add package StyloIssues --version 0.0.2
                    
NuGet\Install-Package StyloIssues -Version 0.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StyloIssues" Version="0.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StyloIssues" Version="0.0.2" />
                    
Directory.Packages.props
<PackageReference Include="StyloIssues" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StyloIssues --version 0.0.2
                    
#r "nuget: StyloIssues, 0.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StyloIssues@0.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StyloIssues&version=0.0.2
                    
Install as a Cake Addin
#tool nuget:?package=StyloIssues&version=0.0.2
                    
Install as a Cake Tool

StyloIssues

CI version NuGet .NET license

A GitHub-issues-backed feedback UX for ASP.NET Core. Users file bug reports and feature requests through a first-class UI on your site; those flow two-way to and from GitHub issues on your repo. GitHub stays the source of truth and gets the full fix-it workflow (labels, PRs, CI); your site provides a nicer front door.

Reusable and framework-agnostic: pluggable identity (ICurrentUser), pluggable form policy (IFeedbackFormPolicy), an optional read-model (IIssueStore), and an optional diagnostic-archive attachment hook (IIssueAttachmentSource). GitHub is the source of truth, so the default build needs no database.

Packages

Package What it is
StyloIssues.Abstractions Interfaces, DTOs, options, the zero-PII reporter marker.
StyloIssues Octokit GitHub gateway, read cache, webhook + reconciler, AddStyloIssues.
StyloIssues.UI Razor Class Library: sb-feedback-* TagHelpers, endpoints, HTMX + Alpine UI.

StyloIssues.UI depends on the other two, so installing it pulls the whole chain.

Install

dotnet add package StyloIssues.UI

Getting started

Three steps: register the services, map the endpoints, and add two host pages that embed the TagHelpers.

1. Register + map (Program.cs)

using StyloIssues;      // AddStyloIssues
using StyloIssues.UI;   // AddStyloIssuesUi, MapStyloIssues

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddStyloIssues(o =>
{
    o.RepoOwner = "your-org";
    o.RepoName  = "your-repo";
    // Simplest auth: a GitHub token (see "Authenticating to GitHub" below).
    o.PersonalAccessToken = builder.Configuration["StyloIssues:Token"] ?? "";
    // Secrets: bind from config / env / user-secrets, never hard-code.
    o.MarkerKey     = builder.Configuration["StyloIssues:MarkerKey"] ?? "";
    o.WebhookSecret = builder.Configuration["StyloIssues:WebhookSecret"] ?? "";
});

builder.Services.AddStyloIssuesUi();     // TagHelpers + ViewComponents + antiforgery
builder.Services.AddRazorPages();        // (or MVC) for your host-owned pages

// Tell StyloIssues who the current user is (see below).
builder.Services.AddScoped<StyloIssues.Abstractions.ICurrentUser, MyCurrentUser>();

var app = builder.Build();

app.UseStaticFiles();
app.MapRazorPages();
app.MapStyloIssues();   // POST write endpoints + the GitHub webhook sink

app.Run();

2. Tell StyloIssues who is filing (ICurrentUser)

Project it from your existing authentication. StableId is an opaque, stable per-user id (only its HMAC ever reaches GitHub, so it stays zero-PII).

using StyloIssues.Abstractions;

public sealed class MyCurrentUser(IHttpContextAccessor http) : ICurrentUser
{
    private System.Security.Claims.ClaimsPrincipal? U => http.HttpContext?.User;
    public bool IsAuthenticated => U?.Identity?.IsAuthenticated ?? false;
    public string? StableId     => U?.FindFirst("sub")?.Value;        // stable, opaque
    public string DisplayName   => U?.Identity?.Name ?? "Anonymous";
    public string? GitHubLogin  => U?.FindFirst("github_login")?.Value; // optional, for @mention
}

3. Add the two host pages (embed the TagHelpers)

The package renders through TagHelpers so you keep full control of your layout. Register the TagHelpers once in Pages/_ViewImports.cshtml:

@addTagHelper *, StyloIssues.UI

Pages/Feedback.cshtml:

@page "/feedback"
<h1>Feedback</h1>
<sb-feedback-form />
<sb-feedback-list />

Pages/FeedbackDetail.cshtml:

@page "/feedback/{number:int}"
@{ var number = Convert.ToInt32(RouteData.Values["number"]); }
<sb-feedback-detail number="@number" />

Finally, in your layout, load the vendored assets and attach the antiforgery token to HTMX requests (see Security notes):

<script src="~/_content/StyloIssues.UI/htmx.min.js"></script>
<script defer src="~/_content/StyloIssues.UI/alpine.min.js"></script>
@Html.AntiForgeryToken()
<script>
document.body.addEventListener('htmx:configRequest', e => {
  const t = document.querySelector('input[name="__RequestVerificationToken"]');
  if (t) e.detail.headers['RequestVerificationToken'] = t.value;
});
</script>

samples/StyloIssues.Sample is a complete, runnable reference for all of the above.

Authenticating to GitHub

Two options; pick one.

  • Personal Access Token (simplest, single-repo / self-host). Set o.PersonalAccessToken to a token with issues read/write on the repo (a fine-grained PAT scoped to the one repo is ideal). The token is used directly and the GitHub-App flow is skipped.
  • GitHub App (multi-tenant / higher rate limits). Leave PersonalAccessToken empty and set o.AppId, o.InstallationId, and o.PrivateKeyPem. StyloIssues mints an installation token per request.

All secrets come from options; bind them from configuration, environment variables, or user-secrets. Never commit them.

Options

Option Purpose Default
RepoOwner / RepoName The GitHub repo issues live in. (required)
PersonalAccessToken GitHub token; when set, skips the App flow. ""
AppId / InstallationId / PrivateKeyPem GitHub App credentials (used when no PAT). 0 / 0 / ""
MarkerKey HMAC key for the opaque per-user reporter marker. "" (required)
WebhookSecret HMAC secret for the GitHub webhook. ""
EnablePublicList Let anonymous visitors list issues. true
CacheTtl Read-cache lifetime. 2 min
ReconcileInterval Webhook-backstop refresh cadence. 10 min
CategoryLabels Map form categories to GitHub labels. {}

Verdict-adaptive form (IFeedbackFormPolicy)

The form can adapt to the visitor. IFeedbackFormPolicy.Evaluate returns a FeedbackFormState: Full (normal form), ChallengeGated (host handles a challenge), or Bare (hide submit, show a message). The write endpoints enforce Bare server-side with a 403 before creating anything, so hiding the button is never the only gate. The default policy returns Full; bind your own to plug in a bot verdict or moderation signal.

Host route convention

  • GET /feedback embeds <sb-feedback-form> and <sb-feedback-list />.
  • GET /feedback/{number:int} embeds <sb-feedback-detail number="@number" />.

These GET pages are host-owned (see step 3) so you control layout and chrome. The package owns the POST write endpoints and the webhook, mapped by MapStyloIssues(), and redirects to /feedback/{number} after a create.

IIssueStore

IIssueStore is a forward-declared seam for a host-supplied read-model (e.g. a SQLite or PostgreSQL projection). The default build binds a no-op; no production wiring drives it in this drop. Implement and register it only if you need a local query surface beyond what the GitHub API already provides via IIssueReader.

Security notes

CSRF: Antiforgery is enforced by the package. AddStyloIssuesUi registers the antiforgery service with HeaderName = "RequestVerificationToken". The form partials render the token as a hidden field via @Html.AntiForgeryToken(). The write handlers (/feedback/new and /feedback/{n}/comment) call antiforgery.ValidateRequestAsync(context) after the auth and bot-gate checks. DisableAntiforgery() is applied only to the HMAC-signed webhook endpoint (/feedback/webhook), which receives server-to-server delivery from GitHub.

Hosts using HTMX must attach the token to HTMX requests with the standard htmx:configRequest listener shown in step 3. No-JS form submissions use the hidden field directly.

Zero-PII: the only reporter identity that reaches GitHub or the reporter marker is HMAC-SHA256(MarkerKey, StableId), never the raw user id.

License

Released into the public domain under The Unlicense.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on StyloIssues:

Package Downloads
StyloIssues.UI

GitHub-issues-backed feedback UX for ASP.NET Core: SSR + HTMX + Alpine, zero-PII, GitHub as the source of truth.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.2 38 7/7/2026
0.0.1 40 7/7/2026