From 07550f6645a6dcf4be503f773dba73f00ed1c971 Mon Sep 17 00:00:00 2001 From: Ashe <149274925+Skylab008@users.noreply.github.com> Date: Tue, 25 Aug 2026 17:40:11 +1000 Subject: [PATCH] chore: prepare StackRunway for open-source contributors --- .editorconfig | 12 ++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 44 +++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ .github/ISSUE_TEMPLATE/feature_request.yml | 28 +++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 16 ++++++++ .github/workflows/quality.yml | 22 +++++++++++ CHANGELOG.md | 20 ++++++++++ CODE_OF_CONDUCT.md | 9 +++++ CONTRIBUTING.md | 34 ++++++++++++++++ README.md | 4 ++ SECURITY.md | 17 ++++++++ assets/stackrunway-preview.webp | Bin 0 -> 31268 bytes package.json | 10 +++++ scripts/check.mjs | 23 +++++++++++ 14 files changed, 244 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/quality.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md create mode 100644 assets/stackrunway-preview.webp create mode 100644 package.json create mode 100644 scripts/check.mjs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1014ba7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..eb31d86 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,44 @@ +name: Bug report +description: Report a reproducible problem in StackRunway +title: "[Bug]: " +labels: [bug] +body: + - type: markdown + attributes: + value: Thanks for helping improve StackRunway. Do not include private financial data or exported JSON contents. + - type: textarea + id: problem + attributes: + label: What happened? + description: Describe the problem and what you expected instead. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce + placeholder: "1. Open…\n2. Add…\n3. Select…" + validations: + required: true + - type: input + id: browser + attributes: + label: Browser and version + placeholder: Safari 26.0, Chrome 140, Firefox 142 + validations: + required: true + - type: dropdown + id: edition + attributes: + label: Edition + options: + - GitHub Pages + - Downloaded index.html + - Forked copy + validations: + required: true + - type: textarea + id: evidence + attributes: + label: Additional context + description: Add a screenshot if useful. Remove account balances and other private information first. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d7f5835 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Security concern + url: https://github.com/Skylab008/StackRunway/security/policy + about: Read the security policy before reporting a vulnerability. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..5503691 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,28 @@ +name: Feature request +description: Propose a focused improvement to StackRunway +title: "[Idea]: " +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: Developer problem + description: What problem should StackRunway solve? + validations: + required: true + - type: textarea + id: outcome + attributes: + label: Desired outcome + description: Describe the result rather than prescribing an implementation. + validations: + required: true + - type: checkboxes + id: principles + attributes: + label: Project fit + options: + - label: This can preserve local-only data and the no-login model. + required: true + - label: This can preserve the downloadable single-file edition. + required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..6375891 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +## What changed + + + +## Verification + + + +- [ ] `npm test` passes +- [ ] The app still opens directly from `index.html` +- [ ] No analytics, tracking, backend or runtime dependency was introduced +- [ ] User-facing changes are recorded in `CHANGELOG.md` + +## Privacy impact + + diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..e5f3081 --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,22 @@ +name: Quality + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Verify standalone application + run: npm test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cbb4467 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +Meaningful user-facing changes to StackRunway are recorded here. + +This project follows [Semantic Versioning](https://semver.org/). + +## [1.0.0] — 2026-08-25 + +### Added + +- Date-aware 30, 60 and 90-day developer expense runway calculations +- Minimum opening balance statement with exact coverage dates +- Most-expensive-week and first-funding-gap insights +- Annual-renewal exposure with monthly-equivalent comparison +- Pause modelling for subscriptions and other expenses +- Weekly, fortnightly, monthly, quarterly, annual and one-off schedules +- Local browser autosave +- Portable JSON export and restore +- Responsive, dependency-free single-file interface +- GitHub Pages edition and MIT Licence diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..2969eb4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Code of Conduct + +StackRunway welcomes thoughtful participation from people of every background and experience level. + +Be respectful, specific and constructive. Discuss the work rather than the person. Harassment, intimidation, discriminatory language, deliberate disruption and the publication of another person's private information are not acceptable. + +Maintainers may edit or remove contributions that breach these standards and may temporarily or permanently restrict participation when necessary to protect the project and its community. + +If conduct needs attention, raise it privately with the repository maintainer where possible. Do not use a public issue to disclose personal or sensitive information. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a172b20 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing to StackRunway + +StackRunway is deliberately small: a private, local-first developer tool that runs from one HTML file. Contributions are welcome when they preserve that clarity. + +## Before opening a pull request + +Search existing issues first. For a material feature or behavioural change, open a feature request before writing code so the direction can be agreed without wasting anyone's time. + +Bug fixes, accessibility improvements, calculation corrections and careful interface refinements are especially valuable. + +## Project principles + +- Keep the application usable without an account, backend or build step. +- Keep user financial data inside the browser. +- Do not add analytics, advertising, tracking or bank connections. +- Avoid runtime dependencies unless there is a compelling security or accessibility reason. +- Preserve the downloadable single-file `index.html` edition. +- Use clear language and accessible interaction patterns. + +## Development + +Open `index.html` directly in a modern browser or serve the repository with any static server. + +Run the repository checks before submitting: + +```bash +npm test +``` + +## Pull requests + +Keep each pull request focused. Explain the user problem, the change and how it was tested. Include screenshots for visible interface changes. Update `CHANGELOG.md` when the change affects users. + +By contributing, you agree that your contribution may be distributed under the repository's [MIT Licence](LICENSE). diff --git a/README.md b/README.md index 5b8e585..6ddc5f6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ --- +![StackRunway developer subscription runway](assets/stackrunway-preview.webp) + Developer expenses rarely arrive in a neat monthly bundle. Hosting renews on Tuesday. API credits run out on Friday. Three annual tools land in the same week and the account balance that looked comfortable suddenly is not. **StackRunway answers the question ordinary expense trackers miss:** @@ -69,4 +71,6 @@ Useful software does not always need a gate, a funnel or another subscription. Released under the [MIT Licence](LICENSE). +[Contributing](CONTRIBUTING.md) · [Security](SECURITY.md) · [Code of Conduct](CODE_OF_CONDUCT.md) · [Changelog](CHANGELOG.md) + Planning estimates only. StackRunway does not predict taxes, exchange-rate movements, price changes or usage-based overages. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..8d5ed4a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +# Security policy + +## Supported version + +The latest version on the `main` branch is supported. + +## Reporting a vulnerability + +Please do not publish exploit details in a public issue. + +Use GitHub's private vulnerability reporting option for this repository when it is available. If it is not available, open a minimal issue stating that you need to report a security concern privately, without including sensitive details or a proof of concept. + +Reports should identify the affected behaviour, the potential impact, reproducible conditions and any suggested remediation. Acknowledgement should be expected within seven days. + +## Security model + +StackRunway has no server, account system, bank connection, analytics or runtime dependency. Expense data is stored in the user's browser and can be exported to a local JSON file. Users are responsible for protecting exported backups and devices on which browser data is stored. diff --git a/assets/stackrunway-preview.webp b/assets/stackrunway-preview.webp new file mode 100644 index 0000000000000000000000000000000000000000..ac6604fc306ddfe09d19929c6dd8329badff4fde GIT binary patch literal 31268 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

_Inq>B%4%6WQL1GCV$4d9)EupFW3~Pu=(3% zyFdldZBt1QO&o!6q7831i)IU2;?7$PyX-1p76a%Mu9!%?*sijK(3MYG*T&Kw=a7b! zT()LSt>!H$iA@WUU~i#;#qg)Kfm5K4;VHrrq-$~9{=#B%#_u~|ySvYLte(~e;ObZT z2*`R4Ce$-qQ%`FZrJ&yA5Y%c4>CKp|0yb@B{rr_i{J2Zis=0ez@C zu&=TDUP3xz&XM;W#*Mt5_Gy_Sik5Za&82Pk^wrbFh~Kg8pM{T8MxA7}u&d}ZKZ`qL zD}q=J&0eP?m>vEWk>`TAGU|+$b0-EF2EJ;g*=M{C=09`#a&(B*_eBzHvI&DmQblk4 zN>fgKgJRj*i8Av>;47AsD?C5=%tK8ud64=gUl{qEL1e8sU2 zlzgulV%piH+yYYfZSm3aSb4U(*dR@&`vS|fMVzv9M!@0Y`o7r36Hr;Izrk0QF0IuE z<4*7G7fyAeW9EUFe(k8g51=jFb?u5uz@xXyp2&@2?$OAStBiAcY<-YKZ)rII++-!gL@iTEd%j~mQ|yFf#9<39tI}c-D(({ggEeq8-9hJ@ z+#Y?BE{9W*u%;BrhOL?6n@z+fdla{cQr(7EAA;LI^*Z7-H`?OE;~>GIYi-vkg2OIL z|A)RC|EsIJPd&#S#azz7kWvZG+9L+y0^_O6)Mr}xXhjkZC#}E%%!EB`-UBdAH7_#t z?=NN@b);SrhijeU``H0~awv+fmZr&bZ)8Tujx>vht%9c>^YJ;np(!K~#)f{~4$l{c zyOwPZ?+Ab^QE*rEjj~5<)@Z9qHRpOc;?Sjg)QYN#L8d=nhLj^~*VjKA-T(kbEeh!X zYzo3u70ywS_Jvb_ns}q0cA=*Zkiae#_uF$@Ca4NN?k|Z!cDOL8Qnl%a zcOa{I&j<8Yt>Pj4TaX3?LFl5OIQk#&TLQ-PoR*2KYdRvTEk3^-M{z=rRbbN?&NGfR4KQ0$us*m9@ZO(0K`0g>SjP;Cgt-Q$_Hd8Bp?RubaE zV#66N3%rE{9+{9XepYFK$mJ8L$S}49kP05Mx;!2fLLQDQk1p{X4*yCJv-khk3Zd`=(xG}mI{W7J#utc8l>Mb|njh9CXA zK2ST;>y!E%;;PpZOyYWs(E!^s24Vs}J>hL;he~A)I)%Snj_|<2sul#mcX6xm`1q3o7(p=&Xwq*I8Ce>R|ci!4Ysdu<4$y zn|#ALHH=VOSrpwZo?59aF*1>}{@Uq^R$WQtlj?DUJjPM?@vQ*PWit{_{H3i{4dCHbK2q6zt>wl z@W%|0OepA^&F=S#=_&9cL`YP*>{12{QKVvFX7jFu-&ecuyc`nHo7-ElJ*TYdE{-vs zh}(o-wO@G!_Z4M2sqjR2@HJT5`;lSM=%aYp>ApE-UHbHn*UImRKa%c?Oy)Nf1h%X1 zzxTfa7&zz08Vw`!Dz_&9wFJ0O8B1pZ{qW(#^Hxahl-Uo%s@jtiQjRu3S`;n8LYs!# zS2Q0YfWzpEf1=`3Fot0oKFCCdK-ZVE8&e;}`T-Fv!u&7W?kGgc)|?37a{m|j0e#Nhf<@9uDz8rOvM+;F(21V9eNBW-i}Yv?(&C-m6!SVw z<>-RMJosF5_q?D?P^CnSRek+Wxo3vM+PPGNnf>I(6Kqx49^U1?gfgJjOi|=pIK0eN zHD=_^U8e189(d)^uXGKB~klkPLq+^^rck@D( zfGD>9;9D#7tPo9yKs_)8<~$u59|LgntE^I~pruO;PqohZ)WGT>f>@qFjQc^>#Z7LY zL0c_f;|PQ_a!#?X`;D@!ZpD2oLdwk?-$ReRoZn%*t-^&ZVz%~;?$IRqQIAu!xK9IPS#E8axqbrk)3)TY+{?q&TErlu0mXcrzQ(-a(J_PA~ea9X5 zGeaQ zPyv-EZ^0eyg+~P#`9TL^rGA|Hk=aCw;Rh^Q`3%O21)$%$yKw}W+fR~A>YwJpf&TY-#ghCQyl?wFbZ25;F8t%_tq(n@iU@ zl$QL4-CbHmH;p1(Py)w)lDnuv84&hngrI%}z>+n)9x)Esd?(-8Sxf*T6BGR>3sDd^ zPX_U^M*1J=4y?%W;V_z_)gBnA7O1!T`Ce@ihIbG|47^NNN@uYPb+mp?y}C@YeJv?Q z@IXH^vg(RG6?nB@%L1zouY+#eBvlCD^6nW@?zZWKBze1tgotUo)>6mPr;67Z<2e5a zv>4oGl_=NSxTg9hME!}n1Xjbh=n{=f&s^ zPx1nbZMDO%!^E%p3_{4-n`^o(an@NWxE0&_Dj3ccu(TiVzFC;W(?J{qH{n5 zOlBft?{q58KEw_qDz&z>3UkaBT2x|%pBw8zy{}rQ)lc*#8I9}%Lz4YDfs|sO$;I}3 zfQa>J$ds|^Df5GToj1nuQ}P$v#yVBr6->>@d230AS98PQ)Ph!%etKlJ8e9?r4YYPr z_Ujwy4`O+SI!`hy!9vt3gBE0LNFBc}xD$J;0OEBwHG++6;7Lv~^$4~4>%Q?Az}Qb+ znfD^gXX5rDSf^&bz{l%(61))!I!cI=M*46&GJ9Dtm$Ei#jLGnUFz)gBu6lq&RYTmw zj5Cyiel?@kyskf%=aOD!zfM70tU}OsS2oC8c#)KJ7Jd z_r_$|Ek%4FV#fP@)$Iuf9h|^D;&pgAbLTq9LOK$ov9a0V5SMl|v_#e*xWaBB@41`% z1j4l3tlrEiHB5t9`pAl!`g4)L9_T8BQ0>R$R;*LCd*jcuyW;lw%2>47D9PVuEY#ji zTNQlt5=A@(t15HzE(4rU2;RL9PEUqAxDCp;TJzm#V7wzxc~^T{la+#%M!f^o~t9{mtR}Lki|3>{X?ZK9};;gthumL|Z@C z;c0O9smB8Wt8oTAkS7^!pBfjp?7Eqfkk*ul1q<^0jSrT4+MFXe+24R`=BvWTi#O03 z^cEwV9bGTl$K=)U%DM$k*tSa%(lE1$PvX5Xx7c+7dLHmUPPw>uK1g{YQXeKFE6jC3RXKI+~kP^9G>2{bB* zH3t#BVs3-)U17%h$8j|>ptjZHp=5~_f1G^SI%E#;{g_YmUm-YSonPUV8^&>Fp5Yxe z98Jf~kvoi$=)mXoDqO0GqnENcl-w}&V7mev{W3(659(8M1TC3A0B%4+ZZTji;SR{w zQAiuP@>y7R=w@!BKI;7{bt9ce5|iQN+0AM1DM$~i@f+-=!Ulyk)iotHT$v@iDEB>Q z$}bNu{_-&GELY>fGMb zgSjNH{EwDPT;K4OVvo)jf6DF`SEs;so)K~PX3a+8UE(6>IpgjHED`7^|OU9ud`J?F9w*+ZC2{H&~FfwUGObcKnt$j3dYU=-weBw@e z>Th$Z@?gyLt8!)Xjr1oK;+1VUNdKYh$>0lt3VbmMM*n=x!>SW1>6Qlz5bm_N=iNV2dlyLHxwavKM@cth+rs|KF$e(lg~y*+0537?A@AGq*M z8bpsMhms{u$^!;s>KdW`NPJzf)Yej100p0T`UzsHXJ|fr-~UGpwqVZA0yGlEI|~Hn zewArgHUUElyU|$C1}!PSJZO`&lZk$AkkxYHWfYnMDLu@6+_#EFS-}U9M^wMD;HrEiTz}A(c=B^#n+X>H*-!BtcVE(y~ zI#jUUIS0BfmFkCe{7h83tU)#_rT(k-RzA{Yd<(xHpdEei;S!OtS_%XIaYN5j;Lr|raKO^VEg?!HDo8Ui{0#ft9B zDQ7K*QiWxs2%DGJ6G3#aHB+lVGWI^jmLPH1=ko2Gu9oV4>iy!bOQ92?QJGp?A8pOx zU=asEjo2L@f6|UR>eQSDr1Tffu`13-+A^ftdI`cDRc4x4`ZeW*hnu1M_e6i|8F?u= z%Nf64C*FOg?u@5UcJ=lG*)iHgDnqE_bL!Fp#2*(i2@Fto%3{XF)^(_CCTSgm|D;+C zCd*7^1@McO{bf9&pM--h1=ajnW-+GRF9%o`KGtzkfwMZagmq|K#_Efvn$A9b*TlR_ zdZ->EBMenn>!RRv(1vx37Q?*uBA+K9MV8~OQ+8^u+`>=lOU*4 zh!Cb`8wq+c%tS$FBQP#}6#b^`Ob@c=B`?iE!ViOzA+_!L*!ksJ@tv%6O91EKgh7gC zF))P+`H=)V$>cRMvTo{|2Gd%g4MBQZ5q}6AT=R}1>iJQLn@HSyp#KosZ;yHB&a`^0 zK29}HbMA1@y|t{L-GdTe4!l_LeDTh%rf>2FfWnT^L3E0JGuV`kIai`4 z)089+J!wj%oJz$>0?O(p1Rgl#MG!@(0fwC7hwx=f{3WNb@UxIuv_n#|P4e?hor20o~0#NouExyWg3CD z^H5FY)R)arQJh}*#9j zB@#xJudZGY@{zRFhZ@&+FLV7e5YCxP+q2FZRa-Mg`|0D*Hv2jZCGf$l&OGHDPqL z_an?LRRQdlQ+>eQ;^W!D(OqYtYH=n$wa2Oj8m%)`0BST)i`3 zBwt|WYp#pOxK7jlUJsLXpB6r-1BgCq2n#C53aa4tDFlM3}^sSZ-7?rCGdm|0H6yGOpYLlfswq=3q6wQ z) zK(wOhLBGvwg;Te&d1+5jRzqigrw|Ax9P#|Q&vwHqRrZMQttH-Qzo>E5 zL+r3(a?O0iXqBA9;A~49l08%wpKwqMJXm(4_=C)g)m`uWPjJI40|%Hu!_C6rh0JyU zHTr`fo_l_v=Cr7SWhJ{!)EJawysFg zM|O3s*l0)vJ)7~^KB4jq1WJjIVwA$=jqi~1`#TC+-?N7+o9FW$F^ZDb4-Rp-`xy1_ z+~W4=HM`c0N~UX#H}ik(UgdRy`Pgi3HbLRBC2WN$QoN}&Y#53!*1?MojVGC;z^flN zwzWv@1#-Er8s(}P0sKdr=v4ok6(VMfm%zy*sFHgUE^v~YOy;OyE;P0S|C~t^@{D<4 z(1VBa`*ITnF^H}xhH<^Skj5p2tXJ-EU>?>rRCG{xX9?k zWAYJy3W|xRajQ9QSoE%j!&b2=vF+d0S%$O^DHzisHpsabmZo-5^(S= zy;yG1b!kt#>ECr+Smn#Sn^tDhxN-;2<5d`>|q;i)F4(*CLvHjA`xkVe_&e7Y)55u4PV{O*?H-6ihEu<~aS zfo^l1M_ZRrD1DUoJ}OjKV1ye{?b*Iwy04@bRi^`))bN4aLKca5 zVgH+T?+Cmz%b#`99;ZO*UO|Hc(YQ6eHp*seJif`BQ$=rBTzS*%&Zb==U;#YtnxI6; z`&kfzubFeR@5+xr4W|<#Km7I082Vy&s)t(EvTRlUOm9P)|D(fTIM6lghNoP~3bR3~ zMo8mD%>($_kLO}_cJGUWm4eUfnBB_xUlR-F6`SF+L#+p66fP#KkYLnkk>zing~eY} zVeR5RK#5eT0;DmVAJ+E|<-f-Zr!+l`fNoV%pjZir)=uf!UUxbCFcOd!0WGL=3%e2$ zc;=+Z#4-Ya;|XHjg5;E#Ux8q0-G+^x%rE)e2o#>WHn~wblo2|^?AGP+G-fY5Yf92_ zs36N(tf0t9pFPwfim0+SoOT$$>!yGWq3bqeJ32>@Vx=y?h4qL0A$5#*eISh!DBypl z@GC<9WtT)>rtR_K(_lJ{S7~{i&AZb5z{rQD<%+u2>|&()qZ)_>;vkVIzkRnrJyv6(hsx!j+2D)j`g< zgX~b#N$R5${+&_IfexGkSx#j$oC3>o1p!P11wf)bCAyUudP4XVGRkth#V^&WY>1@dOCSoKc*~npV(l zof1TIumW?TLUIwXM;hvWp;uAcski?%8S^`NtiwEMAu3WCHbQ^IotHLS(AaKkH7geA zT=Lcjq=ih4XF3}r%*3qy*s#(9-ofdWQWH1F5i>OKPIs0n>2!&xaI;}ffegb43vwa) zK+Mz~UdHRGS=FyR_6l>vA?}D#=v#fv2JgMY8f^YzIra`YArRj%^TNQ?9@}!G7M((K zxK~$&M%q5+uKUtNMfJ`~m~|1a$)p@yo=^V3Trd(I@PTPnNRMo0&-QO_euN*6Qwg(y zOBb}Kta5D8FlEL=8ZwT!dN_BjE_Jsg>J`4bDJZ@YxWctHjBB*2y?H*@OXy$Pe#+&# zx`|{>CT9r=JmR_$%Qc36cQw0JQh#_YHrnP-p?0-24a#`bK|yW^J@{|VDrG7yUt-2J zsOy(>Cs!28ywws4AuP@g!Za$Hkc_@%!uqFGM<#xgVBXM&F=Dn%ZOA2*{^xvh>dBD2 zmefk&ZzyJBm8-8sX}3*$9Xs0EXr^x(H#hY8O5@e@6p1=$6_RB0s?LDbWH^nateUpk zjRp)17?0@c>(T*Ng$U}gg9%7yeOs26H)oB=)BwoZ82;pQ^AKnaeY}T_n84JG`^MM8 zejFi=sB@pk!WAb)o>J3}StRwqXa93EeOQ7v6o{H@ERL9ItCuVmGyqEeTPK10DAqEI zKx%TT$M-agXB)Gj@RJJL5*GlkK4*9bh09u~zy&;a;9!(ep&K$MK`a`$YDmYcELoxw zvx}&av+X9Lr5BIIBH53~oBVbSxAIh7TU(M~l|$FUPQjJQq4|XH@h}!}2jaABOJ;61 zF>!q7+lN1?JAxTxY#};#oi1-oN?62qW>@y8bR2WtN84s~GGFPMj4=$ngWq}HQNof{ zQQp=u1`=GD9=Ib_ATi+#v$!)Y;z3N{My$aP22nxsLQ-F{uqkSc)B6aB9vw-!=;5F! zEE1Qb-R*{JhN#GOE>lm;{F$)ZYa*&N#&$wn?6F`P1?ws0N3_sNEw?- z@KGrygXbDbz!Kzy$ij|WWjTdI`}J0RG~;Sp%Zg2!6!+mxb^!6xs_ClEBXs=0Em6q? zTM>rGK3O&ClrOcq$66Z-h9Tn(`*pHJW7?^uHPJHnicR<=v3-!K%N!0)clug@2QSti zm-4kc2dU?8-MhyY690PbFlT`10`<;{ec$rCWg)?^`!2u*v7GVrvyV-(v%6tAwX>zI z&}c_+ZcQ0{Xp#EU{vd|AyDNytd8gg)dk(QAICRAe;5uVx8%7saY?D{{2M4VF4{Bfr z8jQ#OYM30*eITmc^$FapO;-uTf6&cS{l!4nvmmPbsBfur$jwtYZJIA2lGydp^5%@?rIN>#2}LlsA9pR< zSdOFq*8x@GDqm}cvf?z4Kn;3V5#M#zeozaRK3Hv%oFS6^1qoW?fHmQWd4!(V0}^|6awda02l(0^KX2}oue z;_olw>cd~dhu>r0CY{P0ZRkTe=0v_x!BIqYay70K_<<^z>{R9zLmb z*ivw&1q4d&&rPs7+pu(5=zYm}Y!!2P8WDxob$qZgKFUvXhy8toHQ|{B8ko}it*5N` z<>k&f->ga=?2&TIyaKZDs&&jL9iGPLp&8oA2;3|cU;re~%ZYDASUdTZy9EN_`q~0_skw-eCNuM($uH$!82Z)XT1azH?M4YikXrbY6dp zORDaqIgrcCUaC53*@f}+5SkGmqSQ|5*H#X)^H=Wwc1m1-g`;8Z+`+hZ2UeKbogijy zz(m2zY~-!%R|_C?T;oliUc3h19j6MwKc%#&!?jP9Yv6XAWV&yMHh}6j&y}kZxl>D! zTp<2Es&}Y)u`bG8Pl}kLSVTr(n^WC10WSwO{)MtDZ@m9E^IhkYmDe=DbJG4-XYeIy z?<`o`wnezRvTOSAMfHb_!0-mpFA&T_wb5upO$Dh>C_G zQI0U>h7ktINWT2lu)blUSmRn(aVN4tV!d+cv*Y9D>b9f9FP9_abucqJvQK^>waC=1 zR}@rP@WVo&mF&Hq6srtfcakIgT+9Q89C`C#tnz`2cwM@;_AS}L*2pB&R@ zIORD-y$B@Unh6Ci?8rrKq4lkl0=dSoUkHpZ>a4?b{?mX6=d&Jmn zvu&<9y${hU>eJ6%;+ME0HsLrPu6}@HCcdYty)eldlW)Py6AhwkU%YLW$99tuj z2_N_e-|99dOfY8n02oV*&0g@tt&HA08*=DwKrJ8|@pml^J^Ih;@};I#UtY(;`gF5) zzO={n+BdJ4E-18S1j^Jd(Y|t!U|4Hv=Iq5Q1JbV@EFop4vK;3xxUPi>C?+Li5;>+6s=v!HKeZ; zo%p2mFm%I9!XjdKrkZ>^A{fKKrs51Q2fovXVZ)le@eEmUJ(D|qtUviH~4{w=i(*dJ!eNv@0LYl8Rti2C!&A^F=EY?D(ZnqU0D1#uq&9r>yx;%;0000018Me_ZQ<6cI;G95r)<&x zU{>yrKpa-?j{R-4xOuaN2-#E2*Q=wnizCwQ>1g1{FgPyb)yvE5krBZpuhzN_~e z_-wAe|5v=5V=zGe!y6KrqJ-oPDG!6g$rX$m(F832jV5aNAauT;xmxwwa>vsD6xM;` zWTg=3dw^mmO}?{q&4!K{XIq91@^RsH;IJ} zOqj)cJnSbNK10@2&k+~e{SS(KV6||=I=!O{%I!YoVajp|Yc&2a{`fhBwanPP{w21T zf!POIAAjrUC@=aJG>YW&rwrEDHmwhZSKv;MM)IEkMohmJQE6}j&`+%&T*w{Qa#OpR zGJ&K*J>Ri9gbzOPw2?tn_Rd7z?@TG^cl&%N_cnGiIdGl-Do4z3kcQSj6I=obBOwjT z_6+J;BE^!o_QO^SnXVIg4UVI06~|4CK*`2bbDg%1(A%C8VE+2wS4&mv$$Xt3(-b84 zpbxr000005C%0*6F_-XAGr0G3l=n(wrY}6oI<;fZVeOW4e#?3|;{*E9TP=aABlYC+ z2=8ik{^`Lf1rs_)nC5{pirgKUVtdciyt(cPDE8@X(4hE&v*Ssgq($@w7$(|5p#4>D zWrW14{ZE>rkbiFrP4sCZ61{t^xzbl(^)&|Xd4q{qU(fez8NO~W#BFa6+Ls_F0#hlj zv6H=7!(+j;4l6QZKiVo^Mp%JyCpR-jL{DbY3mM2wXFb*dccUAY9&ub|t$TB6z4{S& zXb%XoOTAxe2l-1*D|ahO<(^#90AZ;-s4$+5Whn1koP$(HI*xc$&rrcZIdwwaa;C^PAYJ7M{$MfIwVAsKN&rckMzbma1& zzRH$B_f6p#$l|1fkzlH9;Po>I|B`0x<8=yhiWf}CY9#lg3T%^FNuE1-mahHb_SLHz zvTCH%CQ97zHr#_AQNQI)+CSOi!nG0fts(BG;cpm%jWpA1BKcV(J36Ox2EKt|B-y0b zELs^hQHeP2kKnlOiOO6PUZAP1Efy&7VK(U51ZYc;nkWP&^s0tKX~kWFjg@7Q%F9!z z${oWI2`!{~t**(?htp+B``Ro=&A@`8MLW1!;`TMfV`cH#0zD=g^Qo(a%xrR_8qEn* zeTK2h480=vJ_oKQR|^Dwt^2+rgRG1bCvPlRzYJwoiqQHIxZ!wvDaR8iyI%N=U{oAB zjW0V7bdgt}Qv>_7(N?xfP-HyPOvdd(wdE&>W09m?DhI1@uHVnFwL!n2p%FfuOu)$X zw*=rQ2IC8nr+ccP{IMo#CZwZ=vc(#du62pY7<5vEG5WqRE?%1GMB1V{0BGR(-DJ_l zJXR*(TI4Tgs#n3oJ$n{XruUF``!xJVM;(_1G_-YA1bH&eVJ^~AK1Ci|8I5Z7E{oP~>0;>t!SpX}FbSF}OfPLxray+G_WH}b#0}?9 zY9bH-Yyw*){-KQzh#LzUq@>ue?9GxS~-jZB)N4hau+(`t! z_9Gai+0BJP{ZTkx*iG-;02P}rqB8{anjbL{tTL$2*hpZW%2aQi?yFi`T6Z*vt1v_x z3_VMBloUt`w)c~54fa=FdIX$?woPbbPeAjEFTBQVvYRX}eC#+_6{k|QwS*2&-=TH7 z$uJpwR$y+LP*ulGjQCp_*ekD~A!#?03tcJQQ}xtD;VKVif+~^zJyUcKY9gVzkfwYd z{PQFE+9c}}&!t#a&GSyR|2g@=oDd^ri?3J3RN(5$cK}?ezyJUU-XWs{%=<@VOjlpy zZ%FcjxpKls{>!pcS|zM*q5vU2UC2S+HtPpK4NG7d8u8DE^=YKT@Xw!9R=@xNkJm)! z0VsDU>in^`@yV;Px#x(VwbC+v(L`z@j{jWA6eZX%(^ro-iqGNo?hC}k%v_|-t>!1|L4jm+Rd16+wX<;9(YGHKclf7IDgM`MEG1aXnkH*}`q3Wy4_QNA7(dUuL?JD*0({3vhVyGus6Q@z*ysG%1=FPTH)Pz}iEwnjl;t z8Z$0ACXf{e(GIj@Fl|@|nV?;3hSJ3@(AF4)=Z8({2Hd=YNAZ zTLPw6HkSUQX;ucnyHCcvbH2L*xKcZ7?=^F9;+b!ayxBIx%(T9&b*bC}TV4)E3<;2~ zC$Fk(xTh@vv(Lb%eyRT^FqVYoQR^&9Gvy`<^02!>G6Mm9c=LWqoON+xiZ}-OHb%<( zFqljxK}7uLD0q}kF7{S4*AZH}rBUH_dgR)xXtjtKbFd+%$T@|r4*(wg_z4|pg`9bg z(n_nuGAFIMNVDPrW>2$pf+OQcRd7_f17pj*EkquZy3XI#9y1DI%I!xtItfsFVSDHR zxC=lBvZU!BA3iHS5q~yPWGRdi8YTI#=R8p)d^BY|S%2~02aLDcw3g>&E%{7(G!Gdr zF)1x={V>`(Mr?-Nf!0?;E;Y}xmGuv*m3mG;l>BCNQCKMkjr`gG;N8YmWKJsZUfOIl z0-i_&@l#Jr0Y_*8^VWm3u`f|Q1ZYOj)9@jFJkbLMKH!x_QKV5Uy$Kgk4e3$4KTyY2 z$`Fi=%YX7oBQbcO#WRDGjgnu&Hh!W8GU>`D*C%fNhMyANl;=7MrZ0xb+d4=8)C22n zQ6xN~Z|_T6dfpDdS1Pg$IZCdYDVZ*#y397*iGC;E1`Mdid4C)fjme~hIS<_78+c zSd-p@0FFzOKmNhu-9xX;FWzk1pA9F@RYr8J0pGdZ-EJgP2St4UX1{%#+7(5 zjUaY zNSO%^$fym$V8__{C-RegGeXV_OPp@u>yhFFeuz{33DM;H|L{J;v$TiKpT9~+M}c6l zIk?G|J(2nXg=N#50(O)Vgu*3Nhy{6B%K@zljRV2MgS$Yquc_LwhA#7HaVW8-v{9lZ+Hn z;{8m3@*myfZ0SWQ(b+hj29;}REm1w+^Ne?wAU=e+2GqG?$FM%(JOdg?zc_jXw@hj_5t3dRWTo2jg}$Ea|8PgVmi>OTD2xiM}DL z&UUA+?-!PgQ+k}TbaA+O!qMuEla#*`5iI>Ysec`)`w)TFx;auns+t}_j77bYiRb8! zg<3tiscYRdYmU_k%SsDu7ggkel8>RQ&5J`eDKVdHMFxJ*oZfT5P4KbsQ@c%;tQ|-p z{DO3rKW=DNTHMWI7ED(#wH+{aWG8kLqB!izS{Z-*Ac0Nq`%(@p#~4Nun#h>G&MZhl zJv|wOmYOv=6P5*(u*(+>cSKuX1We7~P4x3D1?`;^3Q}|{xjcynB96IFQe%(><~0uR zl)rF_mEg%$fAB72XGt1(#uy~Q8hz{Hd(Kp1oy2YG!3N0`4VA*xx)1kFT1J>M`b+~G zf}G1`58oz`Yf1 z3S32IT<1_y&Ngyl;P9iu8t_S`eF4IS9Vg2w8~n|~wGigBCDqui9#naqyel&K-RZT3 z&-rCW{=RiRE#2q^ar#r~1Mz!aheU4nvHva^9^rF*`=dZ9=aEkuoa!KARlMh1c3kx# zaNhq4SA6cXg+|M__>=!S#JglVe~YydNDtDi^N|%(4g1+JvE7-&?@fi3^2uOcDxOyS zDD=Q-8#jx*k9;Yd^Q}@~E&v5?2tu*KoQKFywqHQS-~+MODe}ir0A^i=5KWO>>|&f; zHS-65Ezam5MuFTVv7`Hce%|V%=Vo>Ah{kYx?_&Qov?ZPemo6Wsk53?!3Z?vjRvxGq zoM8YReFG8gS3OUF0M1R-5TR&y?((9&92B)L4&Tl;BHk2XIo~~pDRjQ<;;KM`Vy8O4 z#lK(usUsQkMg%dWmlNUQydM4WFYW|Bg~~>4yJlpis3*g7$F{)dP=J39jL<$nL+6{%BTN1V*tj{Sz3au7uHnN_w(3Y?^2{cWpQG&A z{lap%Ewsap4gWw9BedsD^12cyTOfY6lqXZDZKRFZ){jrq)5(dSUmVV#Sm`6mvSS?v zdzcv#ow^h-l7}Tr4XDKoJ9|&Y`&&-v4UD|uWqg%pC^G6KnNo>$r>qdk8KS}(b;FR; zBVSlSr-c z(3HMy#zWY5^b#PDrL_Pocqc5YON#fU{o_ZW9~#N>)X$7y=zNjH@d0vn$=1J*{^z?j7-txUq+V!~h5sIhIDm{{?KH-o)E5 z(j+k}LoCR{kB0}!uqkrNIz@Yk-HBA%RE2>c%~9-XGqO*iLhswH5|uj2TnEe~u6(x1 zy7ENSA*KHy+*vE$I$}Y%*u(}J2GGX~N+~I}?8xg@fLyo&eVJw8F*W()9tC#;GMx6@ z;OW}XHx%HV!5w z>@(iLUaRi?kHh2f69|g0zF#xA#K$LfSbf(o=hP71-pbX>JS+ZDr*efM6$bkmd;ji z-x00vE%TLb${q{6@M0<=F3g8!F$rWto7;TMIDrtq9azWzTI~gvFLpMVdur!#Sj>}G zWezL>Tk*b?`Bje2q5j;lkpWJCGih`hK;3TxV4QZLWeX+X)X!o)d1ZPNxkreGAqc`2 z{;C~#dbUlhFU`6=oKm@!@t24EOBjvU^8Szf9MYvwGce^uv^1EOed3MqmD%d2n?mvV z{6yzlnzY(ruf4ssq|*CwhHhfX_hZj#ym`#DdDY&mF10XxuZSBey>blRgbdqVbFb;Mqe$oP%`)+4itM%6-D@ajgRriTTbNa+ zwJPUXlh0PozytDl5R!^xfJuXLdUbtwCfrk#RYIA6GN*ZdY zg5)4u=IFScg~ejdnxsL5&(iQafkxiGQrwFP`kEhqHt4}j35V#PlK(#&%R0t2n)kx{ z#%i`+?emfe<`0zSB6-e?36Ylv~_GO0mG=ZN%YWA>?;WfQ9~wIOByGukWk9kco*{Q0ncm1mrYjVDKq z`Do7t;m+z101jY65*0qe*?FaI>egW{;w}JT/i, "index.html must declare HTML5"); +assert.match(html, /StackRunway/, "document title is missing"); +assert.match(html, /id="startDate"/, "runway start control is missing"); +assert.match(html, /data-days="30"/, "30-day runway option is missing"); +assert.match(html, /data-days="60"/, "60-day runway option is missing"); +assert.match(html, /data-days="90"/, "90-day runway option is missing"); +assert.match(html, /Export JSON/, "JSON export control is missing"); +assert.match(html, /Restore JSON/, "JSON restore control is missing"); +assert.doesNotMatch(html, /<script[^>]+src=/i, "runtime script dependencies are not permitted"); +assert.doesNotMatch(html, /<link[^>]+rel=["']stylesheet/i, "runtime stylesheet dependencies are not permitted"); +assert.doesNotMatch(html, /\b(fetch|XMLHttpRequest|WebSocket)\s*\(/, "network calls are not permitted"); + +const scripts = [...html.matchAll(/<script>([\s\S]*?)<\/script>/gi)]; +assert.equal(scripts.length, 1, "exactly one inline application script is expected"); +new vm.Script(scripts[0][1], { filename: "index.html:inline-script" }); + +console.log("StackRunway checks passed: structure, privacy boundaries and JavaScript syntax.");