GenerateEstimatePdf passed /dev/stdin as the input filename so lualatex
treats stdin as a regular input file instead of the terminal — without
it, lualatex emergency-stops after the first line of a multi-line
document (the real root cause of the production "Emergency stop", not
bera/luaotfload which were separate host-package gaps already fixed).
EstimatePdf endpoint calls GenerateEstimatePdf directly and streams the
returned FileInfo, guarding on fo.Exists. QueryAcceptanceRequest is
extracted into its own file under ViewModels/FrontOffice.
Add EstimatePdfGenerationTests: unit tests for SignatureToTikz (vector
draws of both signatures) and GenerateEstimatePdf on a devis signed by
both parties — asserts the PDF exists at the expected DestDir path, is
non-empty, and that only the PDF persists (aux/log cleaned). Skips
cleanly when lualatex is absent.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The estimate foot now renders the provider and client signatures as
TikZ \draw/\fill paths straight from the captured stroke data, in two
side-by-side minipages with a \useasboundingbox so the boxes align
regardless of stroke coverage. A side that hasn't signed shows a
signature rule.
- TeXHelpers.SignatureToTikz walks the PostIt wire format
([k, x0,y0, ...] per stroke, coords in [0, CoordinateMax], y-down)
and emits one \draw per stroke (round caps/joins, 1.5pt), flipping y
so the signature is upright on paper; a single-point tap becomes a
filled dot, mirroring the Avalonia round-capped Polyline.
- FrontOfficeApiController.LoadEstimateForRenderAsync includes
Estimate.Signatures so SignaturePro/SignatureClient resolve.
- Estimate_tex.cshtml drops the \includegraphics of a PNG (which
failed the build when the file was absent) and adds \usepackage{tikz}.
- README: document texlive-pictures (tikz) and texlive-fonts-extra
(bera) as runtime deps; bera.sty missing is the likely cause of the
remaining host Emergency stop.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The detailed LaTeX error (missing .sty, missing \includegraphics
target, …) was being lost: we only redirected stdin, and the engine
writes its diagnostics to the .log file we delete in cleanup. So a
generation failure surfaced only as the terse "Emergency stop" in the
journal, with no clue what actually failed.
Redirect stdout and stderr, and on non-zero exit include them in
GenerationErrorMessage so the caller sees the real engine diagnostic.
Keep the .log file on failure (only clean it on success) so it can be
inspected alongside.
Co-Authored-By: Claude Code <noreply@anthropic.com>
texlive-binaries (lualatex) + texlive-luatex (luaotfload) are required
on the API host for estimate/bill PDF generation. Note that luaotfload
moved from texlive-latex-extra into texlive-luatex on recent Debian.
Co-Authored-By: Claude Code <noreply@anthropic.com>
- List removal: GetOngoingEstimatesAsProvider filters on
ProviderValidationDate so a provider's signed estimate leaves their
list; PostIt refreshes the list on navigation pop-back.
- Title renames: "Devis en attente de signature fournisseur/client"
on both the provider and client sides (HomePage, VM, tests).
- Move the estimate/bill TeX+PDF generation flow from Yavsc.Org into
Yavsc.Api (TeXHelpers, PdfGenerationViewModel, BillViewComponent,
Bill/Estimate templates, _ViewImports). Program.cs wires
AddControllersWithViews + SiteSettings; FrontOfficeApiController
injects IRazorViewEngine (cast to IViewEngine) with optional deps
and exposes estimate.tex/estimate.pdf endpoints keyed by query id.
- PDF generation drives through the Estimate_pdf view (side-effect
render) and compiles via the system lualatex, fed TeX on stdin (no
intermediate .tex), PDF to the bills dir, aux artifacts cleaned so
only the .pdf persists. Resolve the bills dir to an absolute path so
lualatex's -output-directory and working directory agree regardless
of the host cwd (fixes "I can't write on file estimate-1.log").
- PostIt: DownloadAsync on the client + GetEstimateTex/PdfAsync;
Télécharger PDF/TeX buttons on the validation and command-detail
pages via a StorageProvider save helper.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Reopening an already-validated estimate showed an empty pad: the
strokes were stored server-side but never sent back. Embed them in the
estimate payload and load them straight into the pad, in the same wire
format PostIt captured — no separate fetch.
- Server: Signature inverse navs get STJ [JsonIgnore] (cycle-safe
under System.Text.Json, matching ApplicationUser); the four estimate
read endpoints Include(e => e.Signatures) so strokes ride the payload
- Client: EstimateDto.Signatures with EstimateSignatureDto (Strokes,
Type=Pro/0|Client/1, CoordinateMax, CapturedAtUtc)
- PostIt: SignaturePadControl.Load(int[]) replaces the buffer and
raises RedrawRequested; EstimateValidationPageViewModel.LoadExisting
Signature() picks the perspective's side off the estimate and loads
it on Attach
- Tests: server asserts GET estimate/{id} carries the strokes; PostIt
VM tests cover load + side-by-perspective
Co-Authored-By: Claude Code <noreply@anthropic.com>
Distinguish the two parties' acceptances: ProAccepted (provider) and
ClientAccepted (client) replace the single Accepted value for new
acceptances. Accepted=2 is retained as a legacy value so existing DB rows
keep their integer mapping.
- FrontOfficeApiController.AcceptQuery sets status by ActorRole
- BillingController provider-ongoing filters match the new values
- FrontOfficeController (MVC) dashboard counts use ProAccepted||Accepted
- PostIt: IsOngoingStatus, GetStatusRank (client-accepted ranks first for
the pro), and status-badge switches (P/C glyphs) handle the new values
- Tests assert ProAccepted (provider) vs ClientAccepted (client)
Co-Authored-By: Claude Code <noreply@anthropic.com>
Adds the client-side ability to open an estimate from the list, draw a
signature on a reusable SignaturePadView control, and accept or reject it
against /api/v1/front/query/accept|reject. The server resolves the actor
role (provider vs client) from the caller's identity, so the same page works
for either party.
- Yavsc.Api.Client: QueryAcceptanceDto + FrontOfficeApiClient (accept/reject)
- PostIt: extract SignaturePadView UserControl from SignaturePage; new
EstimateValidationPage (VM + view) with Valider/Refuser/Effacer/Retour;
wire OpenEstimate entry point from EstimateListPage; register client/page
in DI + ViewLocator
- Tests: FrontOfficeApiClient + EstimateValidationPageViewModel unit tests;
update EstimateListPageViewModelTests ctor
Co-Authored-By: Claude Code <noreply@anthropic.com>
The POST Delete(UnregisterViewModel) action and the GET Delete() action
both existed in AccountController, but Views/Account/Delete.cshtml was
missing — so GET /Account/Delete had no page to render. Add the
confirmation view (posts back to Delete with the user's UserId, with a
"Confirmer la suppression" button and an "Annuler" cancel link).
Add a smoke test: an authenticated GET /Account/Delete returns 200 and
the confirmation page body.
Co-Authored-By: Claude Code <noreply@anthropic.com>
FrontOfficeApiController.AcceptQuery/RejectQuery now resolve the actor
(provider vs client vs admin) from the query's PerformerId/ClientId and
authorize accordingly — a non-party caller is forbidden. Either party may
accept or reject. Acceptance may carry a PostIt signature, which is staged
on the query's linked estimate (Estimate.CommandId == queryId) and sets the
estimate's ProviderValidationDate / ClientValidationDate; a signature with
no linked estimate, or a signature on a reject, returns 400. Admin bypasses
authz but cannot sign.
The shared signature-staging logic (find-or-add the Signature row by
(EstimateId, Type), write Strokes/CoordinateMax/CapturedAtUtc) is extracted
into EstimateSignaturePersister, and BillingController.Sign now calls it.
The persister owns no disk I/O and no SaveChanges — the caller persists in
its own transaction.
Signature.Strokes is now the sole source of truth: drop the FilePath
property, remove the on-disk EstimateSignatureFileHelper (and its tests),
and add the DropSignatureFilePath migration. The legacy PNG prosign/clisign
flow is independent of Signature.FilePath and is unchanged.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Estimates with status Inserted (provider hasn't worked on them yet)
were showing up in the client's "Mes devis à valider" list. The
asclient endpoint now requires ProviderValidationDate to be set, so
only estimates the provider has actually established and submitted
are visible to the client. The asprovider endpoint is unchanged
(providers still need to see incoming unprocessed requests).
Also fix the anonymous-access redirect on Yavsc.Org: the application
cookie's LoginPath defaulted to /Account/Login, which has no
controller. Anonymous hits to a restricted page now redirect to the
real /signin endpoint (with /signout and /Account/AccessDenied set
to match the existing controller routes).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Quartz sets white body text on a coloured page background, but
--bs-tertiary-bg is near-white in its light variant. The .blogcomment
background now uses a semi-transparent black overlay so white text
stays legible, matching the dropdown hover fix.
Co-Authored-By: Claude Code <noreply@anthropic.com>
The $.psc.blogcomment widget (from the removed wwwroot/js/comment.js)
provided reply-to-comment nesting. Rewrite Details.cshtml to drive the
same /api/v1/blogcomments endpoint with ParentId, delegating reply
clicks so they work across the whole nested tree.
Render comment metadata server-side in the Comment view component
template (the widget used to add it client-side) and Include Author so
nested comments get their author instead of relying on the request-scoped
identity map.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Renders Prev/Next pager from the Index(string id, int skip, int take)
call profile, drops dead .sametitle JS, and replaces the redundant
ReadPermission branch and Minutes>0 modifié check.
Co-Authored-By: Claude Code <noreply@anthropic.com>
BlogSpotService's constructor throws InvalidOperationException
("SiteSettings.Blog is not configured.") when IOptions<SiteSettings>.Blog
is null, so every request to BlogApiController 500'd resolving the
service — all 3 BlogApiMappedClaimsTests were failing with
InternalServerError.
The mapped-claims fixture never configured SiteSettings (the main
BlogsWebServerFixture does, pointing Blog at a per-run temp dir).
Mirror that: Configure<SiteSettings>(s => s.Blog = <temp dir>)
before AddScoped<BlogSpotService>(). These tests are JSON-only
(AttachFiles short-circuits), but the service must be constructible.
Verified: BlogApiMappedClaimsTests 3/3 green; full Yavsc.Blogs.Tests
37/37 green.
Co-Authored-By: Claude <noreply@anthropic.com>
DefineStaticWebAssets.ReadOrCreateCache opens the *.dswa.cache.json
files under obj/.../ via File.OpenRead with no try/catch, so under
parallel solution builds a sharing-violation / TOCTOU race on the cache
file escapes as MSB4018 and fails the whole build (seen on
Yavsc.Blogs during the rc15 solution build).
Set StaticWebAssetsCacheDefineStaticWebAssetsEnabled=false so
CacheManifestPath is empty and ReadOrCreateCache short-circuits to a
no-file-I/O path. The cache is a pure build-speed optimization; no
effect on output correctness, only on incremental rebuild speed of the
wwwroot-light web projects.
Verified: Yavsc.Api (transitive web deps) and Yavsc.Blogs.Tests build
clean, and zero *.dswa.cache.json files are produced.
Refs:
https://source.dot.net/Microsoft.NET.Sdk.StaticWebAssets.Tasks/DefineStaticWebAssets.Cache.cs.htmlhttps://github.com/dotnet/sdk/issues/49039
Co-Authored-By: Claude <noreply@anthropic.com>
Cover the 60 commits since 1.0.8-rc14: blog attachments + Markdown
preview, the Estimate pipeline (list/edition/validation + API + EF
migrations), provider ongoing requests, file-ACL and genericEstimate
migrations, POSTIT_SETTINGS_JSON, billing filtered by user, shared
YavscMessageSender, Gitleaks CI, and the test stabilization wave.
Co-Authored-By: Claude <noreply@anthropic.com>
Expose CreateMultipartContent as public so the blogs backend tests can
assert on its wire shape directly and round-trip it through the live
BlogsWebServerFixture host. New CreateMultipartContentTests:
- shape: blog part is camelCase JSON with WhenWritingNull, one file
part per attachment (filename, Content-Type, octet-stream fallback);
- round-trip: POST the built body to /api/v1/blogspot, assert 201
Created + attachment row + file landed under BlogFilesRoot.
Co-Authored-By: Claude <noreply@anthropic.com>