]> Repositorios git - classgraph.git/log
classgraph.git
5 weeks agoBump version to 0.2 main
Javier Sagredo [Thu, 7 May 2026 01:36:14 +0000 (03:36 +0200)]
Bump version to 0.2

5 weeks agoAdd logo to project
Javier Sagredo [Thu, 7 May 2026 01:29:06 +0000 (03:29 +0200)]
Add logo to project

5 weeks agoRender Haddock markup in the side panel
Javier Sagredo [Thu, 7 May 2026 01:16:37 +0000 (03:16 +0200)]
Render Haddock markup in the side panel

Replace the plain-text rendering of class / instance / family /
fam-instance docstrings with a small markup-aware renderer that
covers the common Haddock constructs:

* @code@ → inline <code>
* 'Foo' / 'Foo.bar' / 'Mod.Foo.bar' → <code>Foo[…]</code>
  (only when the content has identifier shape; English contractions
  like "don't" are left alone)
* "Module.Name" → <code>Module.Name</code>
* __bold__ → <strong>
* /italic/ → <em> (with surrounding-punct check so URLs and Haskell
  operators like /= aren't mangled)
* [text](url) → <a href>
* `> code` bird-foot blocks → <pre>

No new dependencies — six regex passes in a couple of dozen lines
of JS. Anything we don't recognise renders as plain text (visible
@ / quotes), which is mildly ugly but never wrong. CSS gives
inline code an amber tag styling, code blocks a soft-amber box.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoAdd a third reason to the unresolved-faminst placeholder explanation
Javier Sagredo [Thu, 7 May 2026 01:12:24 +0000 (03:12 +0200)]
Add a third reason to the unresolved-faminst placeholder explanation

The user observed cases where a more general fam-instance LHS
(e.g. `data instance LedgerState (ShelleyBlock proto era) mk`)
should cover a more concrete use site (`… EmptyMK`) but the chain
doesn't draw — biUnify's wildcard rule on TyVarRef should handle
this in principle, but kind-arg interactions in the rep TyCon
can leave the unifier looking at structure that doesn't quite
line up. List this as a third reason so the side panel stops
implying the only failures are external-family / nested-args.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRevert per-pivot reduction in replaceFamilyApp
Javier Sagredo [Thu, 7 May 2026 01:08:27 +0000 (03:08 +0200)]
Revert per-pivot reduction in replaceFamilyApp

The previous change ('Reduce pivot's sub-args before unifying in
replaceFamilyApp') traded "F (G a) won't resolve" for "every
concrete fam-instance of F now matches once G a unwraps to a
TyVarRef". That blew up real cases — a ChainDepState use site
intended for Praos / TPraos started matching the BFT and PBFT
data instances too, because biUnify wildcards both sides on
TyVarRef.

Roll back to the conservative behaviour: don't pre-reduce the
pivot's sub-args. Use sites with nested families that we can't
unify directly take the placeholder path. INTERNALS.md updated
to describe the trade-off as a deliberate non-feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRefresh INTERNALS.md for today's data-family + resolution work
Javier Sagredo [Thu, 7 May 2026 01:07:03 +0000 (03:07 +0200)]
Refresh INTERNALS.md for today's data-family + resolution work

* Type family instances section: documents the data-family R:
  rewrite (rep tyvars substituted with use-site args), the resulting
  fiRhs being structurally equal to the LHS (and the reducer
  guarding against the loop), the famInstTyCon-vs-rep_tc quirk,
  and the new fiDataCons/DataConInfo extraction.

* Resolution algorithm: adds the per-pivot reduceTypeArg step in
  replaceFamilyApp (today's fix that lets nested-family use sites
  like F (G a) finally unify with concrete fi candidates), the
  fall-back chain edge to the predicate node when no class instance
  matches, and renumbers the surrounding steps.

* Predicate-node placeholders: documents the empty-args
  use-site skip (no placeholder for unapplied-family references
  like `class HasTables l` with `HasTables LedgerState` in the
  context), and the side panel's two-reason explanation of why a
  placeholder fired.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoReduce pivot's sub-args before unifying in replaceFamilyApp
Javier Sagredo [Thu, 7 May 2026 01:03:58 +0000 (03:03 +0200)]
Reduce pivot's sub-args before unifying in replaceFamilyApp

When a constraint nests type families — `F (G a)` where `G` has a
known type-instance equation — the outer family's pivot search
finds `FamilyApp F [FamilyApp G [...]]`, but biUnify against
`fi.fiArgs = [TyConApp Bar [...]]` fails because tags differ. The
fam-instance is rejected and a placeholder fires, even though the
inner reduction would produce a unifiable shape.

Reduce the pivot's sub-args via reduceTypeArg before biUnify.
For the user's case `ChainDepState (BlockProtocol (ShelleyBlock proto era))`:
* Without this fix: pivot args = `[FamilyApp BlockProtocol [...]]`,
  biUnify against `TyConApp Praos [TyVarRef 0]` fails.
* With this fix: reduced pivot args = `[TyVarRef proto]`, biUnify
  succeeds (TyVarRef wildcards both sides per biUnify semantics),
  and the chain edges from the data fam-instances `ChainDepState
  (Praos c)` / `ChainDepState (TPraos c)` to the predicate node
  finally surface.

Trade-off: when the inner reduction unwraps a family to a TyVarRef
(as above), every concrete data-instance of the outer family now
matches via biUnify's wildcard rule. So instead of a single chain
the user sees one per concrete instance — Praos AND TPraos in this
example. That's consistent with the existing "Are the resolution
chains deterministic?" caveat in INTERNALS.md: ambiguous matches
surface as multiple chain edges and let the reader pick.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoUse abstract names in unresolved-faminst placeholder example
Javier Sagredo [Thu, 7 May 2026 00:58:29 +0000 (02:58 +0200)]
Use abstract names in unresolved-faminst placeholder example

Drop the ChainDepState / BlockProtocol / Praos references and
replace with neutral F / G / Bar shapes. The viewer is meant to be
project-agnostic; baking downstream package vocabulary into the UI
text was a leak.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoHonest unresolved-faminst placeholder text
Javier Sagredo [Thu, 7 May 2026 00:57:23 +0000 (02:57 +0200)]
Honest unresolved-faminst placeholder text

The previous status text said "the equation isn't in this project's
dumps", which is only one of the reasons the placeholder fires. The
other — equally common in real codebases — is that classgraph doesn't
fully simulate GHC's type-family rewrite chain: a use site like
@ChainDepState (BlockProtocol blk)@ won't unify with the
@ChainDepState (Praos c)@ data instance unless we can first reduce
@BlockProtocol blk@ to @Praos _@. If @blk@ stays abstract or the
inner reduction stops, the outer family looks unresolvable even when
the equations are right there in the dumps.

Spell out both reasons in the side panel, with concrete examples and
a hint to open the family node to inspect the equations we *do*
have.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRender multi-arg superclass edge labels as {a} {b}, not (a, b)
Javier Sagredo [Thu, 7 May 2026 00:51:19 +0000 (02:51 +0200)]
Render multi-arg superclass edge labels as {a} {b}, not (a, b)

The tuple-shaped label `(a, b)` looked like the Haskell tuple type
`(a, b)`, but it's actually a positional list of args being passed
to the superclass — not packed into a pair. Switch to space-
separated brace-wrapped form `{a} {b}` so the positional shape
reads at a glance. Single-arg labels stay bare since there's no
ambiguity.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDedupe the side-panel "Subclasses (in this program)" list
Javier Sagredo [Thu, 7 May 2026 00:49:09 +0000 (02:49 +0200)]
Dedupe the side-panel "Subclasses (in this program)" list

A single class can appear twice as a subclass of the same class
(e.g. `class (Foo a, Foo b) => Bar a b` produces two SuperclassEdges
with target Foo). The reverse-index push happens per-edge, which is
the right granularity for chain reasoning, but the side-panel list
was rendering each entry verbatim and so duplicated subclass names.
Add a per-render dedup set keyed on the subclass's qid so each
subclass shows up at most once.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoUpdate README
Javier Sagredo [Thu, 7 May 2026 00:44:09 +0000 (02:44 +0200)]
Update README

5 weeks agoShow constructor names in family-view data fam-instance rows too
Javier Sagredo [Thu, 7 May 2026 00:43:54 +0000 (02:43 +0200)]
Show constructor names in family-view data fam-instance rows too

The family view (drilling into a family directly) had its own
fam-instance label code path that bypassed renderFamInstHead, so
data fam-instance rows showed only their args (no constructor
names). Mirror the renderFamInstHead behaviour: append `= ConA |
ConB` for data fam-instances. The family name itself stays implicit
(it's already at the top of the family view).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoShow only constructor names in data fam-instance node labels
Javier Sagredo [Thu, 7 May 2026 00:39:04 +0000 (02:39 +0200)]
Show only constructor names in data fam-instance node labels

Node labels for data fam-instances now read `Family args = ConA |
ConB` instead of `Family args = ConA Int Bool | ConB { x :: Char }`.
Fields just pad the node visually and are rarely interesting at a
glance. The side panel still renders the full constructor
declaration (name + arg types or record fields) for when the detail
is wanted.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoExtract data-family RHS constructors and show them in the viewer
Javier Sagredo [Thu, 7 May 2026 00:32:36 +0000 (02:32 +0200)]
Extract data-family RHS constructors and show them in the viewer

Adds a DataConInfo record (dcName + dcArgs + dcFieldLabels) and a
fiDataCons :: [DataConInfo] field on FamInstInfo. Schema/JSON
encoding is backwards-compatible: missing fiDataCons defaults to [].

Extract.hs's extractFamInst now walks the rep TyCon's
tyConDataCons (pulled directly out of the DataFamilyInst flavour
constructor — famInstTyCon turned out to be the family TyCon, not
the rep, on at least 9.14.1) and turns each DataCon into a
DataConInfo. The data constructor's arg types are extracted with
the fam-instance's tyvars in scope, so positional TyVarRefs resolve
to the right fiTyVars entries.

Viewer:
* renderFamInstHead now shows `Family args = Con1 t1 t2 | Con2 …`
  for data fam-instances (record syntax: `Con { f1 :: t1, f2 :: t2 }`).
* The fam-instance side panel grows a Constructors entry listing
  each data constructor with its fields.

Demo: the existing Crate Int / Crate Bool / Crate [a] data
fam-instances now render with their CrateInt / CrateBool / CrateList
constructors visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDrop the data-fam-instance "Kind" panel entry entirely
Javier Sagredo [Thu, 7 May 2026 00:24:36 +0000 (02:24 +0200)]
Drop the data-fam-instance "Kind" panel entry entirely

The previous text was wrong on two counts: it labelled the row "Kind"
even though it wasn't showing any kind, and it called the rhs a
"synthetic GHC-internal data constructor" — but the user *did* write
the data constructor (`CrateInt Int`, etc.); the only synthetic part
is the R: TyCon GHC uses internally to represent the instance.

We don't extract data constructors into the schema, so we have
nothing useful to show on the rhs side for data fam-instances. Just
omit the row.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoFamily node labels show tyvars, with (data) on its own italic line
Javier Sagredo [Thu, 7 May 2026 00:22:15 +0000 (02:22 +0200)]
Family node labels show tyvars, with (data) on its own italic line

Family nodes now carry their tyvars in the label (e.g. @BftDSIGN c@,
@LedgerState blk mk@), matching the recent class-node tyvar change.
For data families, @(data)@ goes on a new line via @\n@ in the
label and the cytoscape stylesheet italicises the whole node label
via @node[kind = "family"][?isData]@.

Cytoscape can't style only one line of a label, so the family name
itself ends up italicised too. That's a deliberate trade-off — the
italic doubles as a "this is a data family, not a type family" cue
without requiring any additional iconography.

Render.hs's familyNodeLabel and viewer.js's ensureFamilyNode +
focused-family-node code path all updated together so the
classes-view, instance-view and family-view family nodes all read
the same way.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDon't show "Right-hand side" for data fam-instances
Javier Sagredo [Thu, 7 May 2026 00:20:31 +0000 (02:20 +0200)]
Don't show "Right-hand side" for data fam-instances

GHC's RHS for a @data instance@ is the synthetic R: TyCon (e.g.
@R:CrateInt@), and after the data-family R: rewrite it's the
abstract @FamilyApp Family [args]@ form — structurally equal to the
LHS. Labelling the panel row "Right-hand side: Crate Int" was
misleading because it just repeated what the heading already said.
Replace with a short "Data instance — the right-hand side is a
synthetic GHC-internal data constructor and isn't shown." note for
data fam-instances; type fam-instances keep the real RHS line.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoShow readable type-family flavor names in the side panel
Javier Sagredo [Thu, 7 May 2026 00:19:41 +0000 (02:19 +0200)]
Show readable type-family flavor names in the side panel

Instead of the raw schema tags (OpenFam / ClosedFam / DataFam /
AssocFam), the family panel now reads "Open type family", "Closed
type family", "Data family", or "Associated type family (member of
ParentClass (Module))". Falls back to whatever the tag string is for
unknown flavours.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoSkip placeholder synthesis for unapplied-family use sites
Javier Sagredo [Thu, 7 May 2026 00:15:11 +0000 (02:15 +0200)]
Skip placeholder synthesis for unapplied-family use sites

When a constraint references a family unapplied (passed as a
higher-kinded argument, e.g. @CanUpgradeLedgerTables LedgerState@),
the FamilyApp at the use site has no args. The placeholder
synthesizer would emit @LedgerState = ?@ — informationless beyond
the family name itself, which the family node already shows on its
own. Skip these empty-args use sites so the family node sits in the
graph cleanly without a tautological placeholder hanging off it.

Concretizing the bare reference to e.g. @LedgerState (ShelleyBlock
proto era) mk@ would require constraint-solver-like propagation
across the constraint set (matching @l ~ LedgerState (ShelleyBlock
proto era)@ from elsewhere) — work GHC does at typecheck time but
that we don't preserve in the dump.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoChain real fam-instance to predicate node when no class instance matches
Javier Sagredo [Thu, 7 May 2026 00:11:43 +0000 (02:11 +0200)]
Chain real fam-instance to predicate node when no class instance matches

addFamilyLinksFromArgs only drew a fam-resolves chain edge when
findMatchingInstances returned a satisfying class instance. For
predicates like @Crypto (ProtoCrypto proto)@ or @Show
(CannotForgeError proto)@ where the class lives in a package the
user hasn't extracted, matched is empty and the fam-instance node
ended up disconnected even though we know exactly which predicate
it discharges. Symmetrise with the synthetic-placeholder branch
above: fall back to chaining fi → originPredId so the fam-instance
visibly belongs to that constraint, even when we can't surface the
actual class-instance satisfier.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRender external type families as grey diamonds in the classes view
Javier Sagredo [Thu, 7 May 2026 00:04:51 +0000 (02:04 +0200)]
Render external type families as grey diamonds in the classes view

External-ref stubs in the classes view all defaulted to kind:
"class", so external families like SigDSIGN / HeaderHash / SignKeyKES
showed as grey rounded-rectangles instead of the grey diamonds the
external-family styling expects. We DO know which refs are families
when they came from a FamilyApp use site (collectFamilyAppsInArgs)
or an associated-type list (ciAssocTypes); collect those qids and
tag their external nodes with kind: "family". Bare superclass refs
still default to "class" — same case as before for Show / Typeable /
NoThunks shaped externals.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDocument data-fam-instance skip in reduceTypeArg
Javier Sagredo [Thu, 7 May 2026 00:03:36 +0000 (02:03 +0200)]
Document data-fam-instance skip in reduceTypeArg

Append a bullet to the resolution-determinism section explaining
why reduceTypeArg skips fiIsData entries: the data-family R: rewrite
makes their fiRhs structurally equal to the input FamilyApp, so
naïve recursion looped forever. Also clarify the prior
UndecidableInstances bullet — the reducer DOES recurse, it relies
on structural progress in the RHS to terminate.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoSkip data fam-instances in reduceTypeArg to avoid infinite recursion
Javier Sagredo [Thu, 7 May 2026 00:01:33 +0000 (02:01 +0200)]
Skip data fam-instances in reduceTypeArg to avoid infinite recursion

The data-family R: rewrite makes a data fam-instance's fiRhs
structurally equal to its FamilyApp LHS (e.g. for `data instance
Crate Int`, fiRhs is `FamilyApp Crate [Int]`). reduceTypeArg used
to unify the use site against fiArgs and recurse on
applySubst(fiRhs, subst) — which produced the same FamilyApp shape
again and looped forever for any constraint mentioning a data
fam-instance.

Skip data fam-instances in reduceTypeArg entirely. Their RHS is
opaque (the synthetic R: TyCon pre-rewrite, the circular FamilyApp
post-rewrite); reducing through them never makes progress, only
ever loops. The use-site FamilyApp falls through unchanged, which
addFamilyLinksFromArgs / the predicate-node + chain machinery then
handle as the data-family use site they actually are.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRe-enable node grabbing in cytoscape
Javier Sagredo [Thu, 7 May 2026 00:00:23 +0000 (02:00 +0200)]
Re-enable node grabbing in cytoscape

Reverts the autoungrabify: true addition. Grabbing is useful for
manually rearranging nodes after a dagre layout that crowds parts
of the graph together; the previous commit ('Let drill-in work on
external classes...') is the real fix for the double-click-doing-
nothing bug, so disabling grab was unnecessary.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoLet drill-in work on external classes that have local instances
Javier Sagredo [Wed, 6 May 2026 23:59:19 +0000 (01:59 +0200)]
Let drill-in work on external classes that have local instances

drillInto used to gate on classById.has(n.id()) and silently fall
through to highlightOnly when the double-clicked class wasn't
locally defined. That's the wrong call when the class is external
but instances of it *are* in our dumps (orphan-style, or because
the package defining the instances was extracted while the package
defining the class wasn't) — there's still a useful instance view
to render.

Two changes:

* drillInto now drills in whenever the class is locally defined OR
  has at least one entry in instancesByClass. When neither is true
  (truly nothing to show), it console.warns with the qid so you can
  diagnose why your double-click did nothing.

* buildInstanceView synthesises a minimal ClassInfo from the first
  matching instance's iiClass when classById misses, so the rest of
  the build pipeline (which needs ciSuperclasses, ciAssocTypes,
  ciTyVars …) keeps working with empty defaults. The focused-class
  node ends up as an external-styled stub but the per-instance
  rendering — contexts, superclass requirements, fam-instances — is
  all there.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDisable cytoscape node grabbing so taps fire reliably
Javier Sagredo [Wed, 6 May 2026 23:53:17 +0000 (01:53 +0200)]
Disable cytoscape node grabbing so taps fire reliably

By default cytoscape lets the user drag nodes around. The drag
detector kicks in if the pointer moves more than a few pixels
between mousedown and mouseup, and when that happens cytoscape
suppresses the corresponding 'tap' event — which is what our
hand-rolled double-click handler listens to. So a slightly-jittery
double-click on a class node looked like the node was being dragged
without ever drilling into the instance view.

Set autoungrabify: true at init. Dagre re-runs the layout every time
loadGraph is invoked, so user repositioning of individual nodes was
already throwaway — disabling grabbing is a clean improvement on
both axes.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoFix data-family R: rewrite to substitute rep tyvars with use-site args
Javier Sagredo [Wed, 6 May 2026 23:38:11 +0000 (01:38 +0200)]
Fix data-family R: rewrite to substitute rep tyvars with use-site args

The previous rewrite (commit 723a7c3) called tyConFamInst_maybe and
fed parentArgs straight into typeArg. parentArgs reference the rep
TyCon's *internal* type variables, not the use-site args; without a
substitution they leak through typeArg's TyVar lookup as
@OtherArg "<reptv>"@ because they're absent from boundTvs. So
@R:CrateList Int@ was rewriting to @Crate [<reptv>]@ — which broke
both the rendering (the user saw rep-internal names) and the chain
resolution (replaceFamilyApp's biUnify still succeeded by luck of
the wildcard rule, but the visible label was wrong).

Build a Subst from the rep's tyConTyVars to the actual use-site args
and apply it to parentArgs before recursing. With this fix
@R:CrateList Int@ extracts as @FamilyApp Crate [TyConApp List
[TyConApp Int []]]@ and renders as @Crate [Int]@; the polymorphic
case @R:CrateList a@ in an instance head extracts as
@FamilyApp Crate [TyConApp List [TyVarRef 0]]@ and renders as
@Crate [a]@.

Demo gains a @CrateBound (Crate [a])@ instance so the polymorphic-rep
path is regression-covered.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRender a side panel for external type families
Javier Sagredo [Wed, 6 May 2026 23:21:02 +0000 (01:21 +0200)]
Render a side panel for external type families

Clicking a family node not present in pdTypeFamilies (e.g. SigDSIGN
from a foreign crypto package) used to render nothing — showSelection
silently fell off the renderFamilyPanel branch. Add
renderExternalFamilyPanel that uses the QualName's package + module
to give a useful description, mirroring the external-class panel
treatment.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRewrite data-family R: TyCons to abstract family applications
Javier Sagredo [Wed, 6 May 2026 23:19:42 +0000 (01:19 +0200)]
Rewrite data-family R: TyCons to abstract family applications

GHC represents `data instance Foo Args = …` internally as a
synthetic TyCon `R:FooArgs`. When such a TyCon shows up inside a
constraint or instance head — `NoThunks (R:ConsensusConfigPraos c)`
— the user reads it as the abstract family application
`NoThunks (ConsensusConfig (Praos c))`. Until now we faithfully
reported the rep TyCon, which surfaced the synthetic name.

In typeArg, gate on tyConFamInst_maybe before the regular TyConApp
path: when the TyCon is a data-family instance representation,
emit `FamilyApp parent parentArgs` instead. The viewer's existing
chain logic then naturally connects the @data instance@ row to any
class instance that mentions the abstract family.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoMention package + module in external-class panel description
Javier Sagredo [Wed, 6 May 2026 23:18:12 +0000 (01:18 +0200)]
Mention package + module in external-class panel description

The "External (not defined in this program)" status line was
informationless — the QualName already carried the package and
module the class came from. Surface them in the description with a
hint that the user can extend their plugin run to capture the class
locally if that data would be useful.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoCenter the search box in the topbar
Javier Sagredo [Wed, 6 May 2026 23:17:42 +0000 (01:17 +0200)]
Center the search box in the topbar

Search-wrap was pinned to the right edge with margin-left: auto.
With Focus chips and Mark-orphans gone from the topbar (previous
commit) it had room to breathe — switch to margin: 0 auto so the
flex's auto margins absorb leftover space symmetrically and the
search lands in the middle.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoMove Focus chips and Mark-orphans toggle to the side panel
Javier Sagredo [Wed, 6 May 2026 23:16:52 +0000 (01:16 +0200)]
Move Focus chips and Mark-orphans toggle to the side panel

The topbar got cramped between the title, focus chips, orphan
toggle, and the search input. Moves these two pieces — the focus
chips block and the orphan-marker checkbox — into the side panel,
restyled as panel blocks with consistent borders. The topbar keeps
just back, title, and search.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoShow tyvars on class node labels
Javier Sagredo [Wed, 6 May 2026 23:15:54 +0000 (01:15 +0200)]
Show tyvars on class node labels

Class nodes now read "BftCrypto c" / "Bar a b" instead of just the
class name, in both the classes view (Render.hs's classNodes) and
the instance-view ensureClassNode helpers. External classes — for
which we don't know the tyvars — still fall back to the bare name.

Edge labels (the positional substitution into a superclass's args)
keep using the *origin* class's tyvar names via edgeLabel, so an
arrow `Bar a b ──(b)──► Foo b` reads consistently — same letter on
edge and target node.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDocument the synthetic Family args = ? placeholder
Javier Sagredo [Wed, 6 May 2026 23:14:34 +0000 (01:14 +0200)]
Document the synthetic Family args = ? placeholder

New section covering when the placeholder fires (broader than just
"family is external" — anywhere unification fails), the conditional
chain edge to the originating predicate, the per-(family, args)
dedup key, the implementation as a fake FamInstInfo with
_unresolved/unresolved flags, and the matching grey-dashed styling
shared with external family diamonds.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoMark external families and synthesise unresolved-fam-instance nodes
Javier Sagredo [Wed, 6 May 2026 22:53:50 +0000 (00:53 +0200)]
Mark external families and synthesise unresolved-fam-instance nodes

External families (referenced via FamilyApp but absent from
pdTypeFamilies) now render as grey dashed diamonds, matching the
external-class styling.

When a context predicate or unmatched superclass mentions a
FamilyApp that no real fam-instance can resolve — typically because
the family is external — synthesise a placeholder fam-instance node
"Family args = ?" for each distinct use site. The chain then reads:

    SigDSIGN ──► SigDSIGN Ed448DSIGN = ?  ╶╶►  NoThunks (SigDSIGN Ed448DSIGN)

(the dashed leg is the fam-resolves edge to the originating predicate
node). Side panel for these placeholders explains they're use sites,
not equations. Help legend grows two new rows: external family +
unresolved fam-instance.

addFamilyLinksFromArgs gained two parameters (boundTvs, originPredId)
to render the placeholder labels in the right tyvar context and to
chain placeholders back to the predicate that needed them. The
superclass call site reorders so the unmatched-pred node id is
available before the call.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoShow class→assoc-family links in the instance view
Javier Sagredo [Wed, 6 May 2026 22:49:42 +0000 (00:49 +0200)]
Show class→assoc-family links in the instance view

Until now the assoc-family edges only appeared in the classes view;
the instance view of, say, BftCrypto would show its families
(BftDSIGN, BftHash, …) only as descendants of the *individual*
fam-instances surfaced for each instance. Add an `assoc` arrow
straight from the focused class node to each entry of
`ciAssocTypes`, so the user can see what families the class
declares without having to find an instance that exercises them.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDocument predicate-node dedup caveats in INTERNALS.md
Javier Sagredo [Wed, 6 May 2026 22:36:11 +0000 (00:36 +0200)]
Document predicate-node dedup caveats in INTERNALS.md

New section between the resolution-chain determinism notes and the
"viewer is not doing" wrap-up, covering the structural-hash dedup,
the context-vs-superclass reduction asymmetry, tyvar-index aliasing
across instances, the role-sticky-to-context behaviour, and the
fact that predicate nodes are view-scoped (not persisted).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoUnify unmatched superclass with context predicate node
Javier Sagredo [Wed, 6 May 2026 22:33:10 +0000 (00:33 +0200)]
Unify unmatched superclass with context predicate node

Was: unmatched superclass → class node, edge "needs Foo a b (no
local match)".
Now: unmatched superclass → predicate node (the same one created
from a context constraint when the predicate happens to match,
otherwise a fresh role='extern' grey node), edge labelled
"superclass constraint".

This collapses the case where a constraint appears in *both* the
context and as an unmatched superclass — the user sees one node
with two edges ("instance context" + "superclass constraint")
instead of two disconnected nodes saying the same thing. When the
constraint is purely an external superclass, the grey 'extern'
predicate node makes "must exist somewhere outside this project"
visible at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRender instance context constraints as predicate nodes
Javier Sagredo [Wed, 6 May 2026 22:31:10 +0000 (00:31 +0200)]
Render instance context constraints as predicate nodes

Was: instance → class node, edge labelled "ctx: <args>".
Now: instance → predicate node ("Foo a b" rendered like a constraint),
edge labelled "instance context".

The new node kind 'predicate' deduplicates on a structural id
(class qid + JSON-stringified args), so the same constraint surfaces
once even when it appears in multiple superclass / context slots —
the next commit relies on this to merge unmatched-superclass
requirements with overlapping context predicates.

Side panel grows a renderPredicatePanel that explains the node's role
and links to the underlying class.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoShorten "superclass needed Foo a b" edge label to "superclass constraint"
Javier Sagredo [Wed, 6 May 2026 22:27:58 +0000 (00:27 +0200)]
Shorten "superclass needed Foo a b" edge label to "superclass constraint"

The class + arg shape is already on the target node (matched instance,
or — after the next commit — a predicate node), so spelling it out on
the edge label was redundant. Plain "superclass constraint" describes
the relationship the edge depicts and lets the eye stay on the nodes.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDon't draw the predicate-class node alongside fam-resolves matches
Javier Sagredo [Wed, 6 May 2026 22:27:28 +0000 (00:27 +0200)]
Don't draw the predicate-class node alongside fam-resolves matches

The "fam-resolves" chain pulled the predicate class into the instance
view as a separate node and connected it to the matched instance with
a green "defines" edge — duplicating information already on the
matched-instance label. Drop the class anchor + defines edge here, the
same way commit 3a8e0ac handled the matched-superclass case.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDrop the instance → fam-instance "= (Rhs)" edge
Javier Sagredo [Wed, 6 May 2026 22:14:26 +0000 (00:14 +0200)]
Drop the instance → fam-instance "= (Rhs)" edge

The dotted-purple edge from a focused instance to its associated
fam-instance carried only the RHS label, which the fam-instance
node's own label already shows in full. Removing it cleans up the
instance view; the fam-instance still surfaces because the family
node points at it via fam-defines, which is enough to indicate the
context relevance.

Also drop the now-dead cytoscape style rule and the legend row.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDrop the "satisfies Foo" label on fam-resolves chain edges
Javier Sagredo [Wed, 6 May 2026 22:13:09 +0000 (00:13 +0200)]
Drop the "satisfies Foo" label on fam-resolves chain edges

The teal-dotted edge between a fam-instance and the matched class
instance already speaks for itself — its endpoints (and the side
panel on click) carry the same information the inline label was
trying to add. The label was just visual noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRemove the "via Bar" instance → family edge
Javier Sagredo [Wed, 6 May 2026 22:12:43 +0000 (00:12 +0200)]
Remove the "via Bar" instance → family edge

The dashed light-purple arrow connecting an instance to a referenced
type family was visually opaque — readers couldn't tell what
relationship it depicted. Drop it from addFamilyLinksFromArgs along
with its cytoscape style rule and the help-legend row.

The fam-instance nodes the function surfaces and the fam-resolves
chain edges remain — those are the meaningful continuations from the
matched-superclass-instance node.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDon't draw the superclass class node when an instance matches locally
Javier Sagredo [Wed, 6 May 2026 22:11:33 +0000 (00:11 +0200)]
Don't draw the superclass class node when an instance matches locally

Previously every "superclass needed" arrow pulled in both the matched
instance node *and* a class node for the superclass — joined to the
matched instance by an extra "defines" edge. The class node duplicated
information already on the matched-instance label, just adding clutter.
Restrict the class-node side to the no-local-match branch, which still
needs somewhere for its needs-external edge to terminate.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRelabel "needs Foo" superclass edges to "superclass needed Foo"
Javier Sagredo [Wed, 6 May 2026 22:10:50 +0000 (00:10 +0200)]
Relabel "needs Foo" superclass edges to "superclass needed Foo"

The plain "needs Foo …" edge label in the instance view glossed
which kind of constraint we were displaying (context vs. superclass
vs. ambient). Make it explicit: this edge represents a *superclass*
requirement.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoAdd docs/INTERNALS.md (with neutral examples)
Javier Sagredo [Wed, 6 May 2026 22:10:09 +0000 (00:10 +0200)]
Add docs/INTERNALS.md (with neutral examples)

The internals walkthrough was sitting uncommitted from an earlier
session. Adding it now, with the Cardano-flavoured examples
(Ticked / Shelley / Byron / TxOut/LedgerState) swapped for neutral
Wrap / Foo Int / Element shapes — the doc explains classgraph
internals, not any particular downstream project's vocabulary.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDocument setup for the two Emacs editor-link schemes
Javier Sagredo [Wed, 6 May 2026 22:09:17 +0000 (00:09 +0200)]
Document setup for the two Emacs editor-link schemes

Step-by-step org-protocol setup (init.el snippet + xdg-mime desktop
entry + macOS pointer) plus a DIY emacs:// scheme path with a sample
emacsclient wrapper script. Both end with the "pick from dropdown"
step so the user can see the loop close.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoAdd two Emacs editor-link schemes
Javier Sagredo [Wed, 6 May 2026 22:08:40 +0000 (00:08 +0200)]
Add two Emacs editor-link schemes

emacs://: a freeform URL the user maps to emacsclient via xdg-mime.
emacs-org://: the canonical org-protocol://open-source URL handler
that ships with Emacs once (require 'org-protocol) is loaded.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoDon't recolor the focused type-family node
Javier Sagredo [Wed, 6 May 2026 22:07:56 +0000 (00:07 +0200)]
Don't recolor the focused type-family node

The [?focused] cytoscape rule used to paint every focused node dark
blue, which on the amber family node left dark-on-dark text that
couldn't be read. Split the rule: class nodes still go navy; family
nodes keep their amber palette and indicate focus via a thicker brown
border and larger text.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRename Pin to Focus in the side-panel UI
Javier Sagredo [Wed, 6 May 2026 22:07:21 +0000 (00:07 +0200)]
Rename Pin to Focus in the side-panel UI

Side-panel button is now "🎯 Focus" / "🎯 Unfocus" (was "📌 Pin" /
"📌 Unpin"). Hint text, help legend, counts line, and README all
updated. Internal identifiers (focusSet, pinClass, the panel-btn.pin
CSS class, data-action="toggle-pin") stay as-is — they're invisible
to users and changing them invites bigger churn.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoRender type-level lists as [a, b, c] not (: a (: b []))
Javier Sagredo [Wed, 6 May 2026 22:06:25 +0000 (00:06 +0200)]
Render type-level lists as [a, b, c] not (: a (: b []))

renderArg now special-cases GHC's promoted cons (`:`) / nil (`[]`)
TyCons and the unapplied list TyCon, unfolding nested cons chains
into Haskell list syntax. Demo gets a Bagful + BagList exhibit so
the path is exercised.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoInfer per-package source roots from classgraph-view --input
Javier Sagredo [Wed, 6 May 2026 22:05:20 +0000 (00:05 +0200)]
Infer per-package source roots from classgraph-view --input

Add a --source-root PKG=PATH override and infer a default per-package
root from each --input dir's parent. Schema gains iiDefinedIn /
fiDefinedIn (filled in at merge time from the dump's mdPackage), so
orphan instances resolve under the *defining* package, not the
class's. Render embeds the resulting map; viewer's editor links look
up the per-package root before falling back to the localStorage
override.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
5 weeks agoFix render of tuples and add source-code links
Javier Sagredo [Wed, 6 May 2026 21:35:33 +0000 (23:35 +0200)]
Fix render of tuples and add source-code links

5 weeks agoAdd haddocks to side panel if compiled with -haddock
Javier Sagredo [Wed, 6 May 2026 21:23:23 +0000 (23:23 +0200)]
Add haddocks to side panel if compiled with -haddock

5 weeks agoWIP
Javier Sagredo [Wed, 6 May 2026 20:56:21 +0000 (22:56 +0200)]
WIP

5 weeks agoAdd README.md
Javier Sagredo [Mon, 4 May 2026 00:34:07 +0000 (02:34 +0200)]
Add README.md

5 weeks agoAdd classgraph-plugin-flag.sh for unintrusive plugin usage
Javier Sagredo [Mon, 4 May 2026 00:26:18 +0000 (02:26 +0200)]
Add classgraph-plugin-flag.sh for unintrusive plugin usage

5 weeks agoMark top-level classes (no subclasses) and render them at the top
Javier Sagredo [Sun, 3 May 2026 23:54:00 +0000 (01:54 +0200)]
Mark top-level classes (no subclasses) and render them at the top

5 weeks agoData families: (data) label, hide synthetic RHS, per-family filter; fix instance...
Javier Sagredo [Sun, 3 May 2026 23:36:23 +0000 (01:36 +0200)]
Data families: (data) label, hide synthetic RHS, per-family filter; fix instance source spans

5 weeks agoNormalise package ids when merging dumps
Javier Sagredo [Sun, 3 May 2026 22:49:22 +0000 (00:49 +0200)]
Normalise package ids when merging dumps

5 weeks agoSupport multiple --input directories
Javier Sagredo [Sun, 3 May 2026 22:30:01 +0000 (00:30 +0200)]
Support multiple --input directories

5 weeks agoSide panel: subclass index, pin/mute buttons; search now locates rather than drills in
Javier Sagredo [Sun, 3 May 2026 22:00:13 +0000 (00:00 +0200)]
Side panel: subclass index, pin/mute buttons; search now locates rather than drills in

5 weeks agoResolve family-instance chains to matching class instances; treat (~) as equality...
Javier Sagredo [Sun, 3 May 2026 21:45:36 +0000 (23:45 +0200)]
Resolve family-instance chains to matching class instances; treat (~) as equality class

5 weeks agodemo: cover equality, family-in-context, greetings, and multi-param instance cases
Javier Sagredo [Sun, 3 May 2026 16:43:03 +0000 (18:43 +0200)]
demo: cover equality, family-in-context, greetings, and multi-param instance cases

5 weeks agoviewer: search bar, pin/mute/filter panels, splitter, equality preds, clean type...
Javier Sagredo [Sun, 3 May 2026 16:41:35 +0000 (18:41 +0200)]
viewer: search bar, pin/mute/filter panels, splitter, equality preds, clean type printing

5 weeks agoFirst commit
Javier Sagredo [Sat, 2 May 2026 10:32:23 +0000 (12:32 +0200)]
First commit