Skip to content

Commit b9e0bc8

Browse files
authored
Merge pull request #65 from rzk-lang/provenance
Name the build in the footer and in a crash report
2 parents 9b08051 + 88eb526 commit b9e0bc8

8 files changed

Lines changed: 59 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ All notable changes to this project are documented here. The format follows [Kee
66

77
### Added
88

9+
- The game names the build it is. A footer line carries the engine version, and the commit the content was bundled from when the bundle step knew it, and both are stamped into the "Copy issue report" text. A crash report that cannot be tied to a build is much harder to act on, and the engine version also pins which rzk it was built against, since the changelog records that per release. The commit comes from `RZK_GAME_SOURCE`, which `rzk-game-action` sets to the building commit; a bundle built locally carries nothing and the game says nothing.
10+
911
- Four optional `game.yaml` fields, each replacing something the engine would otherwise say for itself: `subtitle` (the line under the title), `completion` (what a player reads once everything is done, as Markdown, so a game can point at what comes next), `repository` (where the content lives, shown in a footer), and `edit-url` (a template containing `{file}`, which gives every page a link to its own source). `repository` and `edit-url` are for content only. A checker crash stays reported to the engine's own tracker, which is not the author's to receive.
1012

1113
### Fixed

app/Main.hs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import qualified Data.Text as T
3737
import Data.Text.Encoding (encodeUtf8)
3838
import Text.Read (readMaybe)
3939

40+
import Paths_rzk_game (version)
41+
import Data.Version (showVersion)
42+
4043
import qualified RzkGame.Content as Content
4144
import RzkGame.Content (apHomLevel, arrInArrLevel, composeLevel,
4245
composeWitnessLevel, constTriangleLevel,
@@ -97,6 +100,14 @@ mkGameEnv info chapters =
97100
requiresTypingAt :: GameEnv -> Int -> Bool
98101
requiresTypingAt env i = head (drop i (envRequiresTyping env))
99102

103+
-- | Which engine build this is, from the package version.
104+
--
105+
-- Shown in the footer and stamped into a crash report. A report that cannot be
106+
-- tied to a build is much harder to act on, and the version also pins which rzk
107+
-- the engine was built against, since the changelog records that per release.
108+
engineVersion :: T.Text
109+
engineVersion = T.pack (showVersion version)
110+
100111
-- | The engine's own wording, used when a game overrides neither.
101112
defaultSubtitle, defaultCompletion :: T.Text
102113
defaultSubtitle = "An interactive Rzk proof game. Fill the holes."
@@ -1264,13 +1275,21 @@ editLink env itemId = case editLinkFor (envInfo env) itemId of
12641275
-- receive, so the crash panel keeps its own hardcoded tracker and the two are
12651276
-- deliberately kept apart on the page.
12661277
gameFooter :: GameEnv -> View Model Action
1267-
gameFooter env = case gameInfoRepository (envInfo env) of
1268-
Nothing -> text ""
1269-
Just repo -> H.footer_ [ P.class_ "game-footer" ]
1270-
[ text "Found something to fix in this game? "
1271-
, H.a_ [ P.href_ (ms repo), P.target_ "_blank" ] [ text "Its source is here" ]
1272-
, text "."
1273-
]
1278+
gameFooter env = H.footer_ [ P.class_ "game-footer" ]
1279+
( contentLink <> [ H.span_ [ P.class_ "build-line" ] [ text (ms buildLine) ] ] )
1280+
where
1281+
contentLink = case gameInfoRepository (envInfo env) of
1282+
Nothing -> []
1283+
Just repo ->
1284+
[ text "Found something to fix in this game? "
1285+
, H.a_ [ P.href_ (ms repo), P.target_ "_blank" ] [ text "Its source is here" ]
1286+
, text ". "
1287+
]
1288+
-- Which build this is. The engine version pins which rzk it was built
1289+
-- against, since the changelog records that per release, so naming rzk here
1290+
-- as well would only be a second thing to keep in step.
1291+
buildLine = "rzk-game " <> engineVersion
1292+
<> maybe "" (\src -> " · content " <> T.take 7 src) (gameInfoSource (envInfo env))
12741293

12751294
-- | A dismissible banner reporting the result of an import applied at the last
12761295
-- reload (see 'applyPendingImport'): how many items were restored, or why the
@@ -1616,7 +1635,7 @@ puzzleSlotView env m sid ix z =
16161635
-- result shows normally, with any gate notice below it.
16171636
, if m ^. result == Solved && levelGated lvl
16181637
&& not (null gate && null forbidden)
1619-
then text "" else resultView lvl (m ^. editable) (m ^. result)
1638+
then text "" else resultView env lvl (m ^. editable) (m ^. result)
16201639
, gateView lvl (m ^. result) gate
16211640
, forbiddenGateView lvl forbidden
16221641
, hintsView m lvl
@@ -2181,10 +2200,14 @@ slotLabel (SlotPuzzle _ ix z) = tshow (ix + 1) <> ". " <> levelTitle (puzzleLeve
21812200
-- | A ready-to-paste bug report for a checker crash: the level's prelude, the
21822201
-- player's current definition, and the error. The "Copy issue report" button in
21832202
-- the crash panel puts this on the clipboard, GitHub-Markdown formatted.
2184-
crashReport :: Level -> T.Text -> T.Text -> T.Text
2185-
crashReport lvl editable err = T.unlines
2203+
crashReport :: GameEnv -> Level -> T.Text -> T.Text -> T.Text
2204+
crashReport env lvl editable err = T.unlines
21862205
[ "The rzk typechecker crashed in rzk-game."
21872206
, ""
2207+
, "**Build:** rzk-game " <> engineVersion
2208+
<> ", game " <> gameInfoId (envInfo env)
2209+
<> maybe "" (" at " <>) (gameInfoSource (envInfo env))
2210+
, ""
21882211
, "**Prelude:**"
21892212
, "```rzk"
21902213
, T.stripEnd (levelPrelude lvl)
@@ -2228,8 +2251,8 @@ checkStatusView m = H.div_ [] (parenNote <> staleNote)
22282251
message u x = "● Unbalanced brackets: " <> T.intercalate " and " (parts u x) <> "."
22292252
parts u x = [ tshow u <> " unclosed (" | u > 0 ] <> [ tshow x <> " stray )" | x > 0 ]
22302253

2231-
resultView :: Level -> MisoString -> CheckResult -> View Model Action
2232-
resultView lvl editable = \case
2254+
resultView :: GameEnv -> Level -> MisoString -> CheckResult -> View Model Action
2255+
resultView env lvl editable = \case
22332256
NotChecked -> H.pre_ [] [ text "(press Check)" ]
22342257
ParseError e _ -> H.pre_ [ P.class_ "err" ] [ text (ms ("Parse error:\n" <> e)) ]
22352258
-- The rzk type-error formatter is verbose (a "when typechecking …" trace per
@@ -2262,7 +2285,7 @@ resultView lvl editable = \case
22622285
[ H.p_ [] [ text "⚠ The checker hit a bug on this input, not necessarily a mistake in your proof." ]
22632286
, H.p_ []
22642287
[ H.button_ [ P.class_ "copy-report"
2265-
, H.onClick (CopyText (ms (crashReport lvl (fromMisoString editable) e)))
2288+
, H.onClick (CopyText (ms (crashReport env lvl (fromMisoString editable) e)))
22662289
, P.title_ "Copy a ready-to-paste issue report (prelude, your definition, and the error)" ]
22672290
[ text "📋 Copy issue report" ]
22682291
, text " then "

bundler/Bundle.hs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import Data.Text.Encoding (encodeUtf8)
3333
import qualified Data.Text.IO as TIO
3434
import qualified Data.Yaml as Y
3535
import System.Directory (createDirectoryIfMissing)
36-
import System.Environment (getArgs)
36+
import System.Environment (getArgs, lookupEnv)
3737
import System.Exit (die)
3838
import System.FilePath (takeDirectory, (</>))
3939

@@ -56,7 +56,12 @@ main = do
5656

5757
files <- traverse (readLevelFile gameDir) (refMap (fileRefs config))
5858

59-
let bundle = A.object [ "config" A..= config, "files" A..= files ]
59+
-- Where this bundle's content came from, when the caller says. CI knows the
60+
-- commit and the engine does not, so it is stamped in rather than derived, and
61+
-- a local bundle simply carries nothing.
62+
msource <- lookupEnv "RZK_GAME_SOURCE"
63+
let source = [ "source" A..= s | Just s <- [msource], not (null s) ]
64+
bundle = A.object ([ "config" A..= config, "files" A..= files ] <> source)
6065
createDirectoryIfMissing True (takeDirectory outJson)
6166
BL.writeFile outJson (A.encode bundle)
6267
putStrLn ("Wrote " <> outJson <> " ("

rzk-game.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ library
4444
executable rzk-game
4545
main-is: Main.hs
4646
hs-source-dirs: app
47+
-- The package version, so the app can name the build it is. A bug report that
48+
-- cannot be tied to a build is much harder to act on.
49+
other-modules: Paths_rzk_game
50+
autogen-modules: Paths_rzk_game
4751
build-depends:
4852
base
4953
, containers

src/RzkGame/Loader.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ buildGame bs = do
5656
, gameInfoCompletion = gsCompletion cfg
5757
, gameInfoRepository = gsRepository cfg
5858
, gameInfoEditUrl = gsEditUrl cfg
59+
, gameInfoSource = bundleSource bundle
5960
, gameInfoSources = sourceMap files
6061
}
6162
, chapters )

src/RzkGame/Section.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ data GameInfo = GameInfo
137137
, gameInfoCompletion :: Maybe Text -- ^ shown when everything is done
138138
, gameInfoRepository :: Maybe Text -- ^ where the content lives
139139
, gameInfoEditUrl :: Maybe Text -- ^ template with @{file}@, for per-item links
140+
, gameInfoSource :: Maybe Text
141+
-- ^ the commit the content was bundled from, when the bundle step knew it
140142
, gameInfoSources :: Map Text Text
141143
-- ^ item id to source path, so an item can link to its own file. Kept beside
142144
-- the items rather than on them: the built-in game is Haskell values with no
@@ -146,7 +148,7 @@ data GameInfo = GameInfo
146148

147149
-- | A game with only the two facts every game has. The loader fills in the rest.
148150
gameInfo :: Text -> Text -> GameInfo
149-
gameInfo gid title = GameInfo gid title Nothing Nothing Nothing Nothing Map.empty
151+
gameInfo gid title = GameInfo gid title Nothing Nothing Nothing Nothing Nothing Map.empty
150152

151153
-- | The source path an item was loaded from, if the game came from a bundle.
152154
sourceOf :: GameInfo -> Text -> Maybe Text

src/RzkGame/Spec.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,16 @@ import RzkGame.Level (Hint (..), InventoryEntry (..),
6666
data Bundle = Bundle
6767
{ bundleConfig :: GameSpec
6868
, bundleFiles :: Map Text FileSpec
69+
, bundleSource :: Maybe Text
70+
-- ^ where the content came from, stamped by the bundle step when the caller
71+
-- knows (CI does, a local run does not).
6972
} deriving (Eq, Show)
7073

7174
instance FromJSON Bundle where
7275
parseJSON = withObject "Bundle" $ \o -> Bundle
7376
<$> o .: "config"
7477
<*> o .:? "files" .!= mempty
78+
<*> o .:? "source"
7579

7680
-- | The table of contents (the @game.yaml@): a title and the ordered chapters.
7781
data GameSpec = GameSpec

static/index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@
309309
.game-footer { margin: 2.5rem 0 1rem; padding-top: .8rem;
310310
border-top: 1px solid #eee; font-size: .85rem; color: #888; }
311311
.game-footer a { color: #666; }
312+
.build-line { color: #aaa; font-variant-numeric: tabular-nums;
313+
white-space: nowrap; }
312314
.edit-link { margin: .2rem 0 .6rem; font-size: .8rem; }
313315
.edit-link a { color: #999; text-decoration: none; }
314316
.edit-link a:hover { color: #555; text-decoration: underline; }

0 commit comments

Comments
 (0)