diff --git a/.github/workflows/cd-android.yml b/.github/workflows/cd-android.yml
index 2f76398..9fd4722 100644
--- a/.github/workflows/cd-android.yml
+++ b/.github/workflows/cd-android.yml
@@ -29,7 +29,7 @@ jobs:
cache: 'npm'
- name: Install dependencies
- run: npm ci
+ run: npm ci --ignore-scripts
- name: Extract version
id: version
diff --git a/.github/workflows/cd-ios.yml b/.github/workflows/cd-ios.yml
index f0d5b86..f97f872 100644
--- a/.github/workflows/cd-ios.yml
+++ b/.github/workflows/cd-ios.yml
@@ -29,7 +29,7 @@ jobs:
cache: 'npm'
- name: Install dependencies
- run: npm ci
+ run: npm ci --ignore-scripts
- name: Extract version
id: version
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b80717f..02e67af 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
fi
- name: Install dependencies
- run: npm ci
+ run: npm ci --ignore-scripts
- name: Check licenses
run: npx license-checker --failOn "GPL-2.0;GPL-3.0;AGPL-3.0"
diff --git a/coverage/clover.xml b/coverage/clover.xml
new file mode 100644
index 0000000..a17e582
--- /dev/null
+++ b/coverage/clover.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json
new file mode 100644
index 0000000..371ab4c
--- /dev/null
+++ b/coverage/coverage-final.json
@@ -0,0 +1,6 @@
+{"/Users/ren/IdeaProjects/starter-series/react-native-starter/app/(app)/about.js": {"path":"/Users/ren/IdeaProjects/starter-series/react-native-starter/app/(app)/about.js","statementMap":{"0":{"start":{"line":4,"column":2},"end":{"line":14,"column":4}},"1":{"start":{"line":17,"column":15},"end":{"line":37,"column":2}}},"fnMap":{"0":{"name":"AboutScreen","decl":{"start":{"line":3,"column":24},"end":{"line":3,"column":35}},"loc":{"start":{"line":3,"column":38},"end":{"line":15,"column":1}},"line":3}},"branchMap":{},"s":{"0":1,"1":1},"f":{"0":1},"b":{},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"87a05c106167e5e18ee490f9a49790fb575f0596"}
+,"/Users/ren/IdeaProjects/starter-series/react-native-starter/app/(app)/index.js": {"path":"/Users/ren/IdeaProjects/starter-series/react-native-starter/app/(app)/index.js","statementMap":{"0":{"start":{"line":6,"column":19},"end":{"line":6,"column":28}},"1":{"start":{"line":8,"column":2},"end":{"line":30,"column":4}},"2":{"start":{"line":33,"column":15},"end":{"line":81,"column":2}}},"fnMap":{"0":{"name":"HomeScreen","decl":{"start":{"line":5,"column":24},"end":{"line":5,"column":34}},"loc":{"start":{"line":5,"column":37},"end":{"line":31,"column":1}},"line":5}},"branchMap":{"0":{"loc":{"start":{"line":12,"column":9},"end":{"line":12,"column":56}},"type":"cond-expr","locations":[{"start":{"line":12,"column":22},"end":{"line":12,"column":40}},{"start":{"line":12,"column":43},"end":{"line":12,"column":56}}],"line":12}},"s":{"0":1,"1":1,"2":1},"f":{"0":1},"b":{"0":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"358ba6505732e64e8def0a2ef5b11536d4093c64"}
+,"/Users/ren/IdeaProjects/starter-series/react-native-starter/app/(app)/profile.js": {"path":"/Users/ren/IdeaProjects/starter-series/react-native-starter/app/(app)/profile.js","statementMap":{"0":{"start":{"line":5,"column":28},"end":{"line":5,"column":37}},"1":{"start":{"line":7,"column":2},"end":{"line":20,"column":4}},"2":{"start":{"line":23,"column":15},"end":{"line":62,"column":2}}},"fnMap":{"0":{"name":"ProfileScreen","decl":{"start":{"line":4,"column":24},"end":{"line":4,"column":37}},"loc":{"start":{"line":4,"column":40},"end":{"line":21,"column":1}},"line":4}},"branchMap":{"0":{"loc":{"start":{"line":9,"column":7},"end":{"line":13,"column":7}},"type":"cond-expr","locations":[{"start":{"line":10,"column":8},"end":{"line":10,"column":70}},{"start":{"line":12,"column":8},"end":{"line":12,"column":63}}],"line":9},"1":{"loc":{"start":{"line":14,"column":33},"end":{"line":14,"column":58}},"type":"binary-expr","locations":[{"start":{"line":14,"column":33},"end":{"line":14,"column":43}},{"start":{"line":14,"column":47},"end":{"line":14,"column":58}}],"line":14},"2":{"loc":{"start":{"line":15,"column":7},"end":{"line":15,"column":74}},"type":"cond-expr","locations":[{"start":{"line":15,"column":21},"end":{"line":15,"column":67}},{"start":{"line":15,"column":70},"end":{"line":15,"column":74}}],"line":15}},"s":{"0":1,"1":1,"2":1},"f":{"0":1},"b":{"0":[0,1],"1":[1,0],"2":[1,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"d735e630ff221541a838ffed43ac995105c03e8c"}
+,"/Users/ren/IdeaProjects/starter-series/react-native-starter/lib/auth-context.js": {"path":"/Users/ren/IdeaProjects/starter-series/react-native-starter/lib/auth-context.js","statementMap":{"0":{"start":{"line":16,"column":0},"end":{"line":16,"column":38}},"1":{"start":{"line":18,"column":27},"end":{"line":18,"column":44}},"2":{"start":{"line":20,"column":20},"end":{"line":26,"column":2}},"3":{"start":{"line":35,"column":2},"end":{"line":35,"column":47}},"4":{"start":{"line":35,"column":35},"end":{"line":35,"column":47}},"5":{"start":{"line":36,"column":16},"end":{"line":36,"column":34}},"6":{"start":{"line":37,"column":2},"end":{"line":37,"column":38}},"7":{"start":{"line":37,"column":26},"end":{"line":37,"column":38}},"8":{"start":{"line":38,"column":2},"end":{"line":50,"column":3}},"9":{"start":{"line":40,"column":16},"end":{"line":40,"column":62}},"10":{"start":{"line":41,"column":19},"end":{"line":41,"column":63}},"11":{"start":{"line":44,"column":6},"end":{"line":46,"column":56}},"12":{"start":{"line":47,"column":4},"end":{"line":47,"column":28}},"13":{"start":{"line":49,"column":4},"end":{"line":49,"column":16}},"14":{"start":{"line":54,"column":2},"end":{"line":54,"column":27}},"15":{"start":{"line":54,"column":15},"end":{"line":54,"column":27}},"16":{"start":{"line":55,"column":2},"end":{"line":60,"column":4}},"17":{"start":{"line":65,"column":24},"end":{"line":68,"column":2}},"18":{"start":{"line":77,"column":18},"end":{"line":77,"column":42}},"19":{"start":{"line":78,"column":2},"end":{"line":78,"column":29}},"20":{"start":{"line":78,"column":16},"end":{"line":78,"column":29}},"21":{"start":{"line":79,"column":17},"end":{"line":79,"column":39}},"22":{"start":{"line":80,"column":2},"end":{"line":80,"column":28}},"23":{"start":{"line":80,"column":15},"end":{"line":80,"column":28}},"24":{"start":{"line":81,"column":2},"end":{"line":81,"column":79}},"25":{"start":{"line":81,"column":66},"end":{"line":81,"column":79}},"26":{"start":{"line":82,"column":2},"end":{"line":82,"column":67}},"27":{"start":{"line":82,"column":54},"end":{"line":82,"column":67}},"28":{"start":{"line":83,"column":2},"end":{"line":83,"column":14}},"29":{"start":{"line":94,"column":16},"end":{"line":94,"column":41}},"30":{"start":{"line":96,"column":2},"end":{"line":96,"column":24}},"31":{"start":{"line":96,"column":17},"end":{"line":96,"column":24}},"32":{"start":{"line":97,"column":2},"end":{"line":108,"column":3}},"33":{"start":{"line":99,"column":6},"end":{"line":99,"column":75}},"34":{"start":{"line":101,"column":6},"end":{"line":101,"column":83}},"35":{"start":{"line":102,"column":19},"end":{"line":102,"column":41}},"36":{"start":{"line":103,"column":17},"end":{"line":103,"column":39}},"37":{"start":{"line":104,"column":20},"end":{"line":104,"column":62}},"38":{"start":{"line":105,"column":4},"end":{"line":105,"column":67}},"39":{"start":{"line":106,"column":4},"end":{"line":106,"column":24}},"40":{"start":{"line":107,"column":4},"end":{"line":107,"column":11}},"41":{"start":{"line":109,"column":2},"end":{"line":111,"column":3}},"42":{"start":{"line":110,"column":4},"end":{"line":110,"column":64}},"43":{"start":{"line":116,"column":32},"end":{"line":116,"column":46}},"44":{"start":{"line":117,"column":32},"end":{"line":117,"column":46}},"45":{"start":{"line":118,"column":28},"end":{"line":118,"column":42}},"46":{"start":{"line":123,"column":43},"end":{"line":129,"column":4}},"47":{"start":{"line":132,"column":2},"end":{"line":157,"column":9}},"48":{"start":{"line":133,"column":20},"end":{"line":133,"column":25}},"49":{"start":{"line":134,"column":4},"end":{"line":153,"column":9}},"50":{"start":{"line":135,"column":6},"end":{"line":152,"column":7}},"51":{"start":{"line":136,"column":20},"end":{"line":136,"column":63}},"52":{"start":{"line":137,"column":8},"end":{"line":147,"column":9}},"53":{"start":{"line":138,"column":25},"end":{"line":138,"column":40}},"54":{"start":{"line":139,"column":10},"end":{"line":146,"column":11}},"55":{"start":{"line":140,"column":12},"end":{"line":140,"column":31}},"56":{"start":{"line":145,"column":12},"end":{"line":145,"column":59}},"57":{"start":{"line":151,"column":8},"end":{"line":151,"column":42}},"58":{"start":{"line":151,"column":24},"end":{"line":151,"column":42}},"59":{"start":{"line":154,"column":4},"end":{"line":156,"column":6}},"60":{"start":{"line":155,"column":6},"end":{"line":155,"column":23}},"61":{"start":{"line":160,"column":2},"end":{"line":163,"column":17}},"62":{"start":{"line":161,"column":4},"end":{"line":161,"column":26}},"63":{"start":{"line":161,"column":19},"end":{"line":161,"column":26}},"64":{"start":{"line":162,"column":4},"end":{"line":162,"column":69}},"65":{"start":{"line":162,"column":56},"end":{"line":162,"column":67}},"66":{"start":{"line":165,"column":17},"end":{"line":178,"column":28}},"67":{"start":{"line":166,"column":4},"end":{"line":166,"column":19}},"68":{"start":{"line":167,"column":4},"end":{"line":177,"column":5}},"69":{"start":{"line":168,"column":6},"end":{"line":168,"column":24}},"70":{"start":{"line":169,"column":6},"end":{"line":172,"column":7}},"71":{"start":{"line":171,"column":8},"end":{"line":171,"column":74}},"72":{"start":{"line":173,"column":6},"end":{"line":173,"column":26}},"73":{"start":{"line":175,"column":6},"end":{"line":175,"column":18}},"74":{"start":{"line":176,"column":6},"end":{"line":176,"column":14}},"75":{"start":{"line":180,"column":18},"end":{"line":183,"column":8}},"76":{"start":{"line":181,"column":4},"end":{"line":181,"column":51}},"77":{"start":{"line":182,"column":4},"end":{"line":182,"column":21}},"78":{"start":{"line":185,"column":16},"end":{"line":195,"column":3}},"79":{"start":{"line":186,"column":11},"end":{"line":193,"column":5}},"80":{"start":{"line":197,"column":2},"end":{"line":197,"column":79}},"81":{"start":{"line":201,"column":2},"end":{"line":201,"column":33}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":24,"column":10},"end":{"line":24,"column":11}},"loc":{"start":{"line":24,"column":22},"end":{"line":24,"column":24}},"line":24},"1":{"name":"(anonymous_1)","decl":{"start":{"line":25,"column":11},"end":{"line":25,"column":12}},"loc":{"start":{"line":25,"column":23},"end":{"line":25,"column":25}},"line":25},"2":{"name":"decodeIdToken","decl":{"start":{"line":34,"column":16},"end":{"line":34,"column":29}},"loc":{"start":{"line":34,"column":39},"end":{"line":51,"column":1}},"line":34},"3":{"name":"userFromClaims","decl":{"start":{"line":53,"column":9},"end":{"line":53,"column":23}},"loc":{"start":{"line":53,"column":32},"end":{"line":61,"column":1}},"line":53},"4":{"name":"isSessionStillValid","decl":{"start":{"line":76,"column":16},"end":{"line":76,"column":35}},"loc":{"start":{"line":76,"column":63},"end":{"line":84,"column":1}},"line":76},"5":{"name":"handleAuthResult","decl":{"start":{"line":93,"column":22},"end":{"line":93,"column":38}},"loc":{"start":{"line":93,"column":72},"end":{"line":113,"column":1}},"line":93},"6":{"name":"AuthProvider","decl":{"start":{"line":115,"column":16},"end":{"line":115,"column":28}},"loc":{"start":{"line":115,"column":43},"end":{"line":198,"column":1}},"line":115},"7":{"name":"(anonymous_7)","decl":{"start":{"line":132,"column":12},"end":{"line":132,"column":13}},"loc":{"start":{"line":132,"column":18},"end":{"line":157,"column":3}},"line":132},"8":{"name":"(anonymous_8)","decl":{"start":{"line":134,"column":5},"end":{"line":134,"column":6}},"loc":{"start":{"line":134,"column":17},"end":{"line":153,"column":5}},"line":134},"9":{"name":"(anonymous_9)","decl":{"start":{"line":154,"column":11},"end":{"line":154,"column":12}},"loc":{"start":{"line":154,"column":17},"end":{"line":156,"column":5}},"line":154},"10":{"name":"(anonymous_10)","decl":{"start":{"line":160,"column":12},"end":{"line":160,"column":13}},"loc":{"start":{"line":160,"column":18},"end":{"line":163,"column":3}},"line":160},"11":{"name":"(anonymous_11)","decl":{"start":{"line":162,"column":49},"end":{"line":162,"column":50}},"loc":{"start":{"line":162,"column":56},"end":{"line":162,"column":67}},"line":162},"12":{"name":"(anonymous_12)","decl":{"start":{"line":165,"column":29},"end":{"line":165,"column":30}},"loc":{"start":{"line":165,"column":41},"end":{"line":178,"column":3}},"line":165},"13":{"name":"(anonymous_13)","decl":{"start":{"line":180,"column":30},"end":{"line":180,"column":31}},"loc":{"start":{"line":180,"column":42},"end":{"line":183,"column":3}},"line":180},"14":{"name":"(anonymous_14)","decl":{"start":{"line":186,"column":4},"end":{"line":186,"column":5}},"loc":{"start":{"line":186,"column":11},"end":{"line":193,"column":5}},"line":186},"15":{"name":"useAuth","decl":{"start":{"line":200,"column":16},"end":{"line":200,"column":23}},"loc":{"start":{"line":200,"column":26},"end":{"line":202,"column":1}},"line":200}},"branchMap":{"0":{"loc":{"start":{"line":35,"column":2},"end":{"line":35,"column":47}},"type":"if","locations":[{"start":{"line":35,"column":2},"end":{"line":35,"column":47}},{"start":{},"end":{}}],"line":35},"1":{"loc":{"start":{"line":37,"column":2},"end":{"line":37,"column":38}},"type":"if","locations":[{"start":{"line":37,"column":2},"end":{"line":37,"column":38}},{"start":{},"end":{}}],"line":37},"2":{"loc":{"start":{"line":44,"column":6},"end":{"line":46,"column":56}},"type":"cond-expr","locations":[{"start":{"line":45,"column":10},"end":{"line":45,"column":22}},{"start":{"line":46,"column":10},"end":{"line":46,"column":56}}],"line":44},"3":{"loc":{"start":{"line":54,"column":2},"end":{"line":54,"column":27}},"type":"if","locations":[{"start":{"line":54,"column":2},"end":{"line":54,"column":27}},{"start":{},"end":{}}],"line":54},"4":{"loc":{"start":{"line":56,"column":11},"end":{"line":56,"column":31}},"type":"binary-expr","locations":[{"start":{"line":56,"column":11},"end":{"line":56,"column":23}},{"start":{"line":56,"column":27},"end":{"line":56,"column":31}}],"line":56},"5":{"loc":{"start":{"line":57,"column":10},"end":{"line":57,"column":29}},"type":"binary-expr","locations":[{"start":{"line":57,"column":10},"end":{"line":57,"column":21}},{"start":{"line":57,"column":25},"end":{"line":57,"column":29}}],"line":57},"6":{"loc":{"start":{"line":58,"column":13},"end":{"line":58,"column":35}},"type":"binary-expr","locations":[{"start":{"line":58,"column":13},"end":{"line":58,"column":27}},{"start":{"line":58,"column":31},"end":{"line":58,"column":35}}],"line":58},"7":{"loc":{"start":{"line":59,"column":9},"end":{"line":59,"column":27}},"type":"binary-expr","locations":[{"start":{"line":59,"column":9},"end":{"line":59,"column":19}},{"start":{"line":59,"column":23},"end":{"line":59,"column":27}}],"line":59},"8":{"loc":{"start":{"line":76,"column":45},"end":{"line":76,"column":61}},"type":"default-arg","locations":[{"start":{"line":76,"column":51},"end":{"line":76,"column":61}}],"line":76},"9":{"loc":{"start":{"line":78,"column":2},"end":{"line":78,"column":29}},"type":"if","locations":[{"start":{"line":78,"column":2},"end":{"line":78,"column":29}},{"start":{},"end":{}}],"line":78},"10":{"loc":{"start":{"line":80,"column":2},"end":{"line":80,"column":28}},"type":"if","locations":[{"start":{"line":80,"column":2},"end":{"line":80,"column":28}},{"start":{},"end":{}}],"line":80},"11":{"loc":{"start":{"line":81,"column":2},"end":{"line":81,"column":79}},"type":"if","locations":[{"start":{"line":81,"column":2},"end":{"line":81,"column":79}},{"start":{},"end":{}}],"line":81},"12":{"loc":{"start":{"line":81,"column":6},"end":{"line":81,"column":64}},"type":"binary-expr","locations":[{"start":{"line":81,"column":6},"end":{"line":81,"column":36}},{"start":{"line":81,"column":40},"end":{"line":81,"column":64}}],"line":81},"13":{"loc":{"start":{"line":82,"column":2},"end":{"line":82,"column":67}},"type":"if","locations":[{"start":{"line":82,"column":2},"end":{"line":82,"column":67}},{"start":{},"end":{}}],"line":82},"14":{"loc":{"start":{"line":82,"column":6},"end":{"line":82,"column":52}},"type":"binary-expr","locations":[{"start":{"line":82,"column":6},"end":{"line":82,"column":16}},{"start":{"line":82,"column":20},"end":{"line":82,"column":52}}],"line":82},"15":{"loc":{"start":{"line":93,"column":61},"end":{"line":93,"column":70}},"type":"default-arg","locations":[{"start":{"line":93,"column":68},"end":{"line":93,"column":70}}],"line":93},"16":{"loc":{"start":{"line":94,"column":16},"end":{"line":94,"column":41}},"type":"binary-expr","locations":[{"start":{"line":94,"column":16},"end":{"line":94,"column":26}},{"start":{"line":94,"column":30},"end":{"line":94,"column":41}}],"line":94},"17":{"loc":{"start":{"line":96,"column":2},"end":{"line":96,"column":24}},"type":"if","locations":[{"start":{"line":96,"column":2},"end":{"line":96,"column":24}},{"start":{},"end":{}}],"line":96},"18":{"loc":{"start":{"line":97,"column":2},"end":{"line":108,"column":3}},"type":"if","locations":[{"start":{"line":97,"column":2},"end":{"line":108,"column":3}},{"start":{},"end":{}}],"line":97},"19":{"loc":{"start":{"line":99,"column":6},"end":{"line":99,"column":75}},"type":"binary-expr","locations":[{"start":{"line":99,"column":6},"end":{"line":99,"column":31}},{"start":{"line":99,"column":35},"end":{"line":99,"column":67}},{"start":{"line":99,"column":71},"end":{"line":99,"column":75}}],"line":99},"20":{"loc":{"start":{"line":101,"column":6},"end":{"line":101,"column":83}},"type":"binary-expr","locations":[{"start":{"line":101,"column":6},"end":{"line":101,"column":42}},{"start":{"line":101,"column":46},"end":{"line":101,"column":75}},{"start":{"line":101,"column":79},"end":{"line":101,"column":83}}],"line":101},"21":{"loc":{"start":{"line":109,"column":2},"end":{"line":111,"column":3}},"type":"if","locations":[{"start":{"line":109,"column":2},"end":{"line":111,"column":3}},{"start":{},"end":{}}],"line":109},"22":{"loc":{"start":{"line":110,"column":10},"end":{"line":110,"column":63}},"type":"binary-expr","locations":[{"start":{"line":110,"column":10},"end":{"line":110,"column":24}},{"start":{"line":110,"column":28},"end":{"line":110,"column":63}}],"line":110},"23":{"loc":{"start":{"line":137,"column":8},"end":{"line":147,"column":9}},"type":"if","locations":[{"start":{"line":137,"column":8},"end":{"line":147,"column":9}},{"start":{},"end":{}}],"line":137},"24":{"loc":{"start":{"line":137,"column":12},"end":{"line":137,"column":29}},"type":"binary-expr","locations":[{"start":{"line":137,"column":12},"end":{"line":137,"column":22}},{"start":{"line":137,"column":26},"end":{"line":137,"column":29}}],"line":137},"25":{"loc":{"start":{"line":139,"column":10},"end":{"line":146,"column":11}},"type":"if","locations":[{"start":{"line":139,"column":10},"end":{"line":146,"column":11}},{"start":{"line":141,"column":17},"end":{"line":146,"column":11}}],"line":139},"26":{"loc":{"start":{"line":151,"column":8},"end":{"line":151,"column":42}},"type":"if","locations":[{"start":{"line":151,"column":8},"end":{"line":151,"column":42}},{"start":{},"end":{}}],"line":151},"27":{"loc":{"start":{"line":161,"column":4},"end":{"line":161,"column":26}},"type":"if","locations":[{"start":{"line":161,"column":4},"end":{"line":161,"column":26}},{"start":{},"end":{}}],"line":161},"28":{"loc":{"start":{"line":169,"column":6},"end":{"line":172,"column":7}},"type":"if","locations":[{"start":{"line":169,"column":6},"end":{"line":172,"column":7}},{"start":{},"end":{}}],"line":169},"29":{"loc":{"start":{"line":187,"column":12},"end":{"line":187,"column":33}},"type":"binary-expr","locations":[{"start":{"line":187,"column":12},"end":{"line":187,"column":25}},{"start":{"line":187,"column":29},"end":{"line":187,"column":33}}],"line":187},"30":{"loc":{"start":{"line":188,"column":14},"end":{"line":188,"column":37}},"type":"binary-expr","locations":[{"start":{"line":188,"column":14},"end":{"line":188,"column":29}},{"start":{"line":188,"column":33},"end":{"line":188,"column":37}}],"line":188}},"s":{"0":1,"1":1,"2":1,"3":8,"4":1,"5":7,"6":7,"7":1,"8":6,"9":6,"10":6,"11":6,"12":5,"13":1,"14":2,"15":0,"16":2,"17":1,"18":2,"19":2,"20":0,"21":2,"22":2,"23":0,"24":2,"25":0,"26":2,"27":0,"28":2,"29":4,"30":4,"31":0,"32":4,"33":2,"34":2,"35":2,"36":2,"37":2,"38":2,"39":2,"40":2,"41":2,"42":1,"43":9,"44":9,"45":9,"46":9,"47":9,"48":3,"49":3,"50":3,"51":3,"52":3,"53":2,"54":2,"55":2,"56":0,"57":3,"58":3,"59":3,"60":3,"61":9,"62":3,"63":3,"64":0,"65":0,"66":9,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":9,"76":1,"77":1,"78":9,"79":9,"80":9,"81":9},"f":{"0":0,"1":0,"2":8,"3":2,"4":2,"5":4,"6":9,"7":3,"8":3,"9":3,"10":3,"11":0,"12":0,"13":1,"14":9,"15":9},"b":{"0":[1,7],"1":[1,6],"2":[6,0],"3":[0,2],"4":[2,0],"5":[2,0],"6":[2,0],"7":[2,0],"8":[2],"9":[0,2],"10":[0,2],"11":[0,2],"12":[2,2],"13":[0,2],"14":[2,2],"15":[4],"16":[4,4],"17":[0,4],"18":[2,2],"19":[2,0,0],"20":[2,0,0],"21":[1,1],"22":[1,0],"23":[2,1],"24":[3,3],"25":[2,0],"26":[3,0],"27":[3,0],"28":[0,0],"29":[9,5],"30":[9,5]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"0d74882ff50b43669c4b3ff3ca40a54986aa7413"}
+,"/Users/ren/IdeaProjects/starter-series/react-native-starter/lib/env.js": {"path":"/Users/ren/IdeaProjects/starter-series/react-native-starter/lib/env.js","statementMap":{"0":{"start":{"line":7,"column":12},"end":{"line":7,"column":56}},"1":{"start":{"line":8,"column":12},"end":{"line":8,"column":56}},"2":{"start":{"line":9,"column":16},"end":{"line":9,"column":64}},"3":{"start":{"line":11,"column":31},"end":{"line":15,"column":1}},"4":{"start":{"line":23,"column":2},"end":{"line":29,"column":3}},"5":{"start":{"line":24,"column":4},"end":{"line":28,"column":6}}},"fnMap":{"0":{"name":"assertGoogleEnv","decl":{"start":{"line":22,"column":16},"end":{"line":22,"column":31}},"loc":{"start":{"line":22,"column":34},"end":{"line":30,"column":1}},"line":22}},"branchMap":{"0":{"loc":{"start":{"line":23,"column":2},"end":{"line":29,"column":3}},"type":"if","locations":[{"start":{"line":23,"column":2},"end":{"line":29,"column":3}},{"start":{},"end":{}}],"line":23}},"s":{"0":1,"1":1,"2":1,"3":1,"4":0,"5":0},"f":{"0":0},"b":{"0":[0,0]},"_coverageSchema":"1a1c01bbd47fc00a2c39e90264f33305004495a9","hash":"e7074b3074143f99cfa47c9de7a05562862e3678"}
+}
diff --git a/coverage/lcov-report/app/(app)/about.js.html b/coverage/lcov-report/app/(app)/about.js.html
new file mode 100644
index 0000000..57c27fe
--- /dev/null
+++ b/coverage/lcov-report/app/(app)/about.js.html
@@ -0,0 +1,196 @@
+
+
+
+
+
+ Code coverage report for app/(app)/about.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+ Statements
+ 2/2
+
+
+
+
+ 100%
+ Branches
+ 0/0
+
+
+
+
+ 100%
+ Functions
+ 1/1
+
+
+
+
+ 100%
+ Lines
+ 2/2
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ import { View, Text, StyleSheet } from 'react-native';
+
+export default function AboutScreen() {
+ return (
+ <View style={styles.container}>
+ <Text style={styles.title}>About</Text>
+ <Text style={styles.text}>
+ Built with Expo + Expo Router.
+ </Text>
+ <Text style={styles.text}>
+ CI/CD powered by GitHub Actions + EAS Build.
+ </Text>
+ </View>
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 24,
+ backgroundColor: '#fff',
+ },
+ title: {
+ fontSize: 28,
+ fontWeight: 'bold',
+ color: '#4630EB',
+ marginBottom: 16,
+ },
+ text: {
+ fontSize: 16,
+ color: '#666',
+ textAlign: 'center',
+ marginBottom: 8,
+ },
+});
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/app/(app)/index.html b/coverage/lcov-report/app/(app)/index.html
new file mode 100644
index 0000000..1045736
--- /dev/null
+++ b/coverage/lcov-report/app/(app)/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+ Code coverage report for app/(app)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+ Statements
+ 8/8
+
+
+
+
+ 50%
+ Branches
+ 4/8
+
+
+
+
+ 100%
+ Functions
+ 3/3
+
+
+
+
+ 100%
+ Lines
+ 8/8
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+
+
+
+ File
+
+ Statements
+
+ Branches
+
+ Functions
+
+ Lines
+
+
+
+
+ about.js
+
+
+
+ 100%
+ 2/2
+ 100%
+ 0/0
+ 100%
+ 1/1
+ 100%
+ 2/2
+
+
+
+ index.js
+
+
+
+ 100%
+ 3/3
+ 50%
+ 1/2
+ 100%
+ 1/1
+ 100%
+ 3/3
+
+
+
+ profile.js
+
+
+
+ 100%
+ 3/3
+ 50%
+ 3/6
+ 100%
+ 1/1
+ 100%
+ 3/3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/app/(app)/index.js.html b/coverage/lcov-report/app/(app)/index.js.html
new file mode 100644
index 0000000..d14af98
--- /dev/null
+++ b/coverage/lcov-report/app/(app)/index.js.html
@@ -0,0 +1,328 @@
+
+
+
+
+
+ Code coverage report for app/(app)/index.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+ Statements
+ 3/3
+
+
+
+
+ 50%
+ Branches
+ 1/2
+
+
+
+
+ 100%
+ Functions
+ 1/1
+
+
+
+
+ 100%
+ Lines
+ 3/3
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+
+
+
+
+1x
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ import { View, Text, StyleSheet, Pressable } from 'react-native';
+import { Link } from 'expo-router';
+import { useAuth } from '../../lib/auth-context';
+
+export default function HomeScreen() {
+ const { user } = useAuth();
+
+ return (
+ <View style={styles.container}>
+ <Text style={styles.title}>My App</Text>
+ <Text style={styles.subtitle}>
+ {user?.name ? `Hi, ${user.name}` : 'Get Started'}
+ </Text>
+ <Text style={styles.description}>
+ Edit <Text style={styles.code}>app/(app)/index.js</Text> to start building.
+ </Text>
+ <View style={styles.row}>
+ <Link href="/about" asChild>
+ <Pressable style={styles.button}>
+ <Text style={styles.buttonText}>About</Text>
+ </Pressable>
+ </Link>
+ <Link href="/profile" asChild>
+ <Pressable style={[styles.button, styles.buttonSecondary]}>
+ <Text style={styles.buttonText}>Profile</Text>
+ </Pressable>
+ </Link>
+ </View>
+ </View>
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 24,
+ backgroundColor: '#fff',
+ },
+ title: {
+ fontSize: 32,
+ fontWeight: 'bold',
+ color: '#4630EB',
+ marginBottom: 8,
+ },
+ subtitle: {
+ fontSize: 20,
+ color: '#666',
+ marginBottom: 16,
+ },
+ description: {
+ fontSize: 16,
+ color: '#999',
+ textAlign: 'center',
+ marginBottom: 32,
+ },
+ code: {
+ fontFamily: 'monospace',
+ backgroundColor: '#f0f0f0',
+ color: '#333',
+ },
+ row: {
+ flexDirection: 'row',
+ gap: 12,
+ },
+ button: {
+ backgroundColor: '#4630EB',
+ paddingHorizontal: 24,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ buttonSecondary: {
+ backgroundColor: '#6B5BE6',
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+});
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/app/(app)/profile.js.html b/coverage/lcov-report/app/(app)/profile.js.html
new file mode 100644
index 0000000..805b025
--- /dev/null
+++ b/coverage/lcov-report/app/(app)/profile.js.html
@@ -0,0 +1,271 @@
+
+
+
+
+
+ Code coverage report for app/(app)/profile.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100%
+ Statements
+ 3/3
+
+
+
+
+ 50%
+ Branches
+ 3/6
+
+
+
+
+ 100%
+ Functions
+ 1/1
+
+
+
+
+ 100%
+ Lines
+ 3/3
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+
+
+
+1x
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ import { View, Text, StyleSheet, Pressable, Image } from 'react-native';
+import { useAuth } from '../../lib/auth-context';
+
+export default function ProfileScreen() {
+ const { user, signOut } = useAuth();
+
+ return (
+ <View style={styles.container}>
+ {user?.picture ? (
+ <Image source={{ uri: user.picture }} style={styles.avatar} />
+ ) : (
+ <View style={[styles.avatar, styles.avatarFallback]} />
+ )}
+ <Text style={styles.name}>{user?.name ?? 'Signed in'} </Text>
+ {user?.email ? <Text style={styles.email}>{user.email}</Text> : null}
+ <Pressable style={styles.button} onPress={signOut}>
+ <Text style={styles.buttonText}>Sign out</Text>
+ </Pressable>
+ </View>
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ padding: 24,
+ backgroundColor: '#fff',
+ },
+ avatar: {
+ width: 96,
+ height: 96,
+ borderRadius: 48,
+ marginBottom: 16,
+ },
+ avatarFallback: {
+ backgroundColor: '#e0e0e0',
+ },
+ name: {
+ fontSize: 22,
+ fontWeight: '700',
+ color: '#222',
+ marginBottom: 4,
+ },
+ email: {
+ fontSize: 15,
+ color: '#666',
+ marginBottom: 32,
+ },
+ button: {
+ backgroundColor: '#d1372b',
+ paddingHorizontal: 24,
+ paddingVertical: 12,
+ borderRadius: 8,
+ },
+ buttonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+});
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/base.css b/coverage/lcov-report/base.css
new file mode 100644
index 0000000..f418035
--- /dev/null
+++ b/coverage/lcov-report/base.css
@@ -0,0 +1,224 @@
+body, html {
+ margin:0; padding: 0;
+ height: 100%;
+}
+body {
+ font-family: Helvetica Neue, Helvetica, Arial;
+ font-size: 14px;
+ color:#333;
+}
+.small { font-size: 12px; }
+*, *:after, *:before {
+ -webkit-box-sizing:border-box;
+ -moz-box-sizing:border-box;
+ box-sizing:border-box;
+ }
+h1 { font-size: 20px; margin: 0;}
+h2 { font-size: 14px; }
+pre {
+ font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ margin: 0;
+ padding: 0;
+ -moz-tab-size: 2;
+ -o-tab-size: 2;
+ tab-size: 2;
+}
+a { color:#0074D9; text-decoration:none; }
+a:hover { text-decoration:underline; }
+.strong { font-weight: bold; }
+.space-top1 { padding: 10px 0 0 0; }
+.pad2y { padding: 20px 0; }
+.pad1y { padding: 10px 0; }
+.pad2x { padding: 0 20px; }
+.pad2 { padding: 20px; }
+.pad1 { padding: 10px; }
+.space-left2 { padding-left:55px; }
+.space-right2 { padding-right:20px; }
+.center { text-align:center; }
+.clearfix { display:block; }
+.clearfix:after {
+ content:'';
+ display:block;
+ height:0;
+ clear:both;
+ visibility:hidden;
+ }
+.fl { float: left; }
+@media only screen and (max-width:640px) {
+ .col3 { width:100%; max-width:100%; }
+ .hide-mobile { display:none!important; }
+}
+
+.quiet {
+ color: #7f7f7f;
+ color: rgba(0,0,0,0.5);
+}
+.quiet a { opacity: 0.7; }
+
+.fraction {
+ font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
+ font-size: 10px;
+ color: #555;
+ background: #E8E8E8;
+ padding: 4px 5px;
+ border-radius: 3px;
+ vertical-align: middle;
+}
+
+div.path a:link, div.path a:visited { color: #333; }
+table.coverage {
+ border-collapse: collapse;
+ margin: 10px 0 0 0;
+ padding: 0;
+}
+
+table.coverage td {
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+}
+table.coverage td.line-count {
+ text-align: right;
+ padding: 0 5px 0 20px;
+}
+table.coverage td.line-coverage {
+ text-align: right;
+ padding-right: 10px;
+ min-width:20px;
+}
+
+table.coverage td span.cline-any {
+ display: inline-block;
+ padding: 0 5px;
+ width: 100%;
+}
+.missing-if-branch {
+ display: inline-block;
+ margin-right: 5px;
+ border-radius: 3px;
+ position: relative;
+ padding: 0 4px;
+ background: #333;
+ color: yellow;
+}
+
+.skip-if-branch {
+ display: none;
+ margin-right: 10px;
+ position: relative;
+ padding: 0 4px;
+ background: #ccc;
+ color: white;
+}
+.missing-if-branch .typ, .skip-if-branch .typ {
+ color: inherit !important;
+}
+.coverage-summary {
+ border-collapse: collapse;
+ width: 100%;
+}
+.coverage-summary tr { border-bottom: 1px solid #bbb; }
+.keyline-all { border: 1px solid #ddd; }
+.coverage-summary td, .coverage-summary th { padding: 10px; }
+.coverage-summary tbody { border: 1px solid #bbb; }
+.coverage-summary td { border-right: 1px solid #bbb; }
+.coverage-summary td:last-child { border-right: none; }
+.coverage-summary th {
+ text-align: left;
+ font-weight: normal;
+ white-space: nowrap;
+}
+.coverage-summary th.file { border-right: none !important; }
+.coverage-summary th.pct { }
+.coverage-summary th.pic,
+.coverage-summary th.abs,
+.coverage-summary td.pct,
+.coverage-summary td.abs { text-align: right; }
+.coverage-summary td.file { white-space: nowrap; }
+.coverage-summary td.pic { min-width: 120px !important; }
+.coverage-summary tfoot td { }
+
+.coverage-summary .sorter {
+ height: 10px;
+ width: 7px;
+ display: inline-block;
+ margin-left: 0.5em;
+ background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
+}
+.coverage-summary .sorted .sorter {
+ background-position: 0 -20px;
+}
+.coverage-summary .sorted-desc .sorter {
+ background-position: 0 -10px;
+}
+.status-line { height: 10px; }
+/* yellow */
+.cbranch-no { background: yellow !important; color: #111; }
+/* dark red */
+.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
+.low .chart { border:1px solid #C21F39 }
+.highlighted,
+.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
+ background: #C21F39 !important;
+}
+/* medium red */
+.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
+/* light red */
+.low, .cline-no { background:#FCE1E5 }
+/* light green */
+.high, .cline-yes { background:rgb(230,245,208) }
+/* medium green */
+.cstat-yes { background:rgb(161,215,106) }
+/* dark green */
+.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
+.high .chart { border:1px solid rgb(77,146,33) }
+/* dark yellow (gold) */
+.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
+.medium .chart { border:1px solid #f9cd0b; }
+/* light yellow */
+.medium { background: #fff4c2; }
+
+.cstat-skip { background: #ddd; color: #111; }
+.fstat-skip { background: #ddd; color: #111 !important; }
+.cbranch-skip { background: #ddd !important; color: #111; }
+
+span.cline-neutral { background: #eaeaea; }
+
+.coverage-summary td.empty {
+ opacity: .5;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ line-height: 1;
+ color: #888;
+}
+
+.cover-fill, .cover-empty {
+ display:inline-block;
+ height: 12px;
+}
+.chart {
+ line-height: 0;
+}
+.cover-empty {
+ background: white;
+}
+.cover-full {
+ border-right: none !important;
+}
+pre.prettyprint {
+ border: none !important;
+ padding: 0 !important;
+ margin: 0 !important;
+}
+.com { color: #999 !important; }
+.ignore-none { color: #999; font-weight: normal; }
+
+.wrapper {
+ min-height: 100%;
+ height: auto !important;
+ height: 100%;
+ margin: 0 auto -48px;
+}
+.footer, .push {
+ height: 48px;
+}
diff --git a/coverage/lcov-report/block-navigation.js b/coverage/lcov-report/block-navigation.js
new file mode 100644
index 0000000..530d1ed
--- /dev/null
+++ b/coverage/lcov-report/block-navigation.js
@@ -0,0 +1,87 @@
+/* eslint-disable */
+var jumpToCode = (function init() {
+ // Classes of code we would like to highlight in the file view
+ var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
+
+ // Elements to highlight in the file listing view
+ var fileListingElements = ['td.pct.low'];
+
+ // We don't want to select elements that are direct descendants of another match
+ var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
+
+ // Selector that finds elements on the page to which we can jump
+ var selector =
+ fileListingElements.join(', ') +
+ ', ' +
+ notSelector +
+ missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
+
+ // The NodeList of matching elements
+ var missingCoverageElements = document.querySelectorAll(selector);
+
+ var currentIndex;
+
+ function toggleClass(index) {
+ missingCoverageElements
+ .item(currentIndex)
+ .classList.remove('highlighted');
+ missingCoverageElements.item(index).classList.add('highlighted');
+ }
+
+ function makeCurrent(index) {
+ toggleClass(index);
+ currentIndex = index;
+ missingCoverageElements.item(index).scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ inline: 'center'
+ });
+ }
+
+ function goToPrevious() {
+ var nextIndex = 0;
+ if (typeof currentIndex !== 'number' || currentIndex === 0) {
+ nextIndex = missingCoverageElements.length - 1;
+ } else if (missingCoverageElements.length > 1) {
+ nextIndex = currentIndex - 1;
+ }
+
+ makeCurrent(nextIndex);
+ }
+
+ function goToNext() {
+ var nextIndex = 0;
+
+ if (
+ typeof currentIndex === 'number' &&
+ currentIndex < missingCoverageElements.length - 1
+ ) {
+ nextIndex = currentIndex + 1;
+ }
+
+ makeCurrent(nextIndex);
+ }
+
+ return function jump(event) {
+ if (
+ document.getElementById('fileSearch') === document.activeElement &&
+ document.activeElement != null
+ ) {
+ // if we're currently focused on the search input, we don't want to navigate
+ return;
+ }
+
+ switch (event.which) {
+ case 78: // n
+ case 74: // j
+ goToNext();
+ break;
+ case 66: // b
+ case 75: // k
+ case 80: // p
+ goToPrevious();
+ break;
+ }
+ };
+})();
+window.addEventListener('keydown', jumpToCode);
diff --git a/coverage/lcov-report/favicon.png b/coverage/lcov-report/favicon.png
new file mode 100644
index 0000000..c1525b8
Binary files /dev/null and b/coverage/lcov-report/favicon.png differ
diff --git a/coverage/lcov-report/index.html b/coverage/lcov-report/index.html
new file mode 100644
index 0000000..bfa4d28
--- /dev/null
+++ b/coverage/lcov-report/index.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+ Code coverage report for All files
+
+
+
+
+
+
+
+
+
+
+
+
All files
+
+
+
+ 80.2%
+ Statements
+ 77/96
+
+
+
+
+ 62.5%
+ Branches
+ 45/72
+
+
+
+
+ 75%
+ Functions
+ 15/20
+
+
+
+
+ 85.88%
+ Lines
+ 73/85
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+
+
+
+ File
+
+ Statements
+
+ Branches
+
+ Functions
+
+ Lines
+
+
+
+
+ app/(app)
+
+
+
+ 100%
+ 8/8
+ 50%
+ 4/8
+ 100%
+ 3/3
+ 100%
+ 8/8
+
+
+
+ lib
+
+
+
+ 78.4%
+ 69/88
+ 64.06%
+ 41/64
+ 70.58%
+ 12/17
+ 84.41%
+ 65/77
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/lib/auth-context.js.html b/coverage/lcov-report/lib/auth-context.js.html
new file mode 100644
index 0000000..4e92b23
--- /dev/null
+++ b/coverage/lcov-report/lib/auth-context.js.html
@@ -0,0 +1,691 @@
+
+
+
+
+
+ Code coverage report for lib/auth-context.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 79.26%
+ Statements
+ 65/82
+
+
+
+
+ 66.12%
+ Branches
+ 41/62
+
+
+
+
+ 75%
+ Functions
+ 12/16
+
+
+
+
+ 85.91%
+ Lines
+ 61/71
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84
+85
+86
+87
+88
+89
+90
+91
+92
+93
+94
+95
+96
+97
+98
+99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130
+131
+132
+133
+134
+135
+136
+137
+138
+139
+140
+141
+142
+143
+144
+145
+146
+147
+148
+149
+150
+151
+152
+153
+154
+155
+156
+157
+158
+159
+160
+161
+162
+163
+164
+165
+166
+167
+168
+169
+170
+171
+172
+173
+174
+175
+176
+177
+178
+179
+180
+181
+182
+183
+184
+185
+186
+187
+188
+189
+190
+191
+192
+193
+194
+195
+196
+197
+198
+199
+200
+201
+202
+203
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+1x
+
+1x
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8x
+7x
+7x
+6x
+
+6x
+6x
+
+
+6x
+
+
+5x
+
+1x
+
+
+
+
+2x
+2x
+
+
+
+
+
+
+
+
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+2x
+2x
+2x
+2x
+2x
+2x
+2x
+
+
+
+
+
+
+
+
+
+
+4x
+
+4x
+4x
+
+2x
+
+2x
+2x
+2x
+2x
+2x
+2x
+2x
+
+2x
+1x
+
+
+
+
+
+9x
+9x
+9x
+
+
+
+
+9x
+
+
+
+
+
+
+
+
+9x
+3x
+3x
+3x
+3x
+3x
+2x
+2x
+2x
+
+
+
+
+
+
+
+
+
+
+3x
+
+
+3x
+3x
+
+
+
+
+9x
+3x
+
+
+
+9x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+9x
+1x
+1x
+
+
+9x
+9x
+
+
+
+
+
+
+
+
+
+
+9x
+
+
+
+9x
+
+ import {
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
+import * as SecureStore from 'expo-secure-store';
+import * as WebBrowser from 'expo-web-browser';
+import * as Google from 'expo-auth-session/providers/google';
+import { assertGoogleEnv, googleClientIds } from './env';
+
+// Required on web + some managed flows: completes the auth popup when the
+// redirect lands back in the app. No-op on native. Safe to call at module load.
+WebBrowser.maybeCompleteAuthSession();
+
+export const STORAGE_KEY = 'auth.session.v1';
+
+const AuthContext = createContext({
+ user: null,
+ loading: true,
+ error: null,
+ signIn: as ync () => {},
+ signOut: as ync () => {},
+});
+
+/**
+ * Decode a JWT payload WITHOUT verifying the signature.
+ * Safe here because `id_token` came from Google over TLS via expo-auth-session's
+ * PKCE flow — we only read the public claims (email, name, picture).
+ * Never trust this output for authorization on a server; verify on the backend.
+ */
+export function decodeIdToken(idToken) {
+ if (typeof idToken !== 'string') return null;
+ const parts = idToken.split('.');
+ if (parts.length !== 3) return null;
+ try {
+ // base64url -> base64
+ const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
+ const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
+ // `atob` is available in Hermes/React Native and Node >= 16.
+ const json =
+ typeof atob === 'function'
+ ? atob(padded)
+ : Buffer.from(padded, 'base64').toString('utf8');
+ return JSON.parse(json);
+ } catch {
+ return null;
+ }
+}
+
+function userFromClaims(claims) {
+ I if (!claims) return null;
+ return {
+ email: claims.email ?? null,
+ name: claims.name ?? null,
+ picture: claims.picture ?? null,
+ sub: claims.sub ?? null,
+ };
+}
+
+// Google's documented `iss` values. Anything else means we're rehydrating
+// a token from a different IdP — not what this starter assumes.
+const ALLOWED_ISSUERS = new Set([
+ 'https://accounts.google.com',
+ 'accounts.google.com',
+]);
+
+/**
+ * Reject sessions whose id_token has expired or wasn't issued by Google.
+ * The token came from Google over TLS via PKCE, so this isn't a server-grade
+ * check — it's the *client*'s self-defense against a stolen device replaying
+ * an old SecureStore blob. Returns true when the session should be honoured.
+ */
+export function isSessionStillValid(session, now = Date.now()) {
+ const idToken = session?.tokens?.idToken;
+ I if (!idToken) return false;
+ const claims = decodeIdToken(idToken);
+ I if (!claims) return false;
+ I if (typeof claims.exp !== 'number' || claims.exp * 1000 <= now) return false;
+ I if (claims.iss && !ALLOWED_ISSUERS.has(claims.iss)) return false;
+ return true;
+}
+
+/**
+ * Pure post-auth handler. Exported so tests can cover the result -> user pipeline
+ * without mocking `useAuthRequest` internals.
+ *
+ * `setSession` receives `{ user, tokens }` on success, `null` on dismissal, or
+ * throws on error.
+ */
+export async function handleAuthResult(response, setSession, deps = {}) {
+ const store = deps.store ?? SecureStore;
+
+ I if (!response) return;
+ if (response.type === 'success') {
+ const idToken =
+ response.params?.id_token ?? response.authentication?.idToken ?? null;
+ const accessToken =
+ response.authentication?.accessToken ?? response.params?.access_token ?? null;
+ const claims = decodeIdToken(idToken);
+ const user = userFromClaims(claims);
+ const session = { user, tokens: { idToken, accessToken } };
+ await store.setItemAsync(STORAGE_KEY, JSON.stringify(session));
+ setSession(session);
+ return;
+ }
+ if (response.type === 'error') {
+ throw response.error ?? new Error('Google sign-in failed.');
+ }
+ // `dismiss` / `cancel` -> no-op
+}
+
+export function AuthProvider({ children }) {
+ const [session, setSession] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ // `useAuthRequest` returns [request, response, promptAsync].
+ // We build the config lazily and guard on env at signIn time so tests / dev
+ // without client IDs don't crash at import.
+ const [request, response, promptAsync] = Google.useAuthRequest({
+ clientId: googleClientIds.webClientId,
+ iosClientId: googleClientIds.iosClientId,
+ androidClientId: googleClientIds.androidClientId,
+ webClientId: googleClientIds.webClientId,
+ scopes: ['openid', 'profile', 'email'],
+ });
+
+ // Restore from SecureStore on mount.
+ useEffect(() => {
+ let cancelled = false;
+ (async () => {
+ try {
+ const raw = await SecureStore.getItemAsync(STORAGE_KEY);
+ if (!cancelled && raw) {
+ const parsed = JSON.parse(raw);
+ if (isSessionStillValid(parsed)) {
+ setSession(parsed);
+ } else E {
+ // Token expired or issuer mismatch — clear the blob so a fresh
+ // sign-in is forced. Without this, an expired token persists
+ // and the UI treats the user as signed in indefinitely.
+ await SecureStore.deleteItemAsync(STORAGE_KEY);
+ }
+ }
+ } catch {
+ // corrupt blob -> treat as signed out
+ } finally {
+ E if (!cancelled) setLoading(false);
+ }
+ })();
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ // React to the auth response.
+ useEffect(() => {
+ E if (!response) return;
+ handleAuthResult(response, setSession).catch((e ) => setError(e)) ;
+ }, [response]);
+
+ const signIn = useCallback(as ync () => {
+ setError(null);
+ try {
+ assertGoogleEnv();
+ if (!request) {
+ // `useAuthRequest` not ready yet — retry shortly.
+ throw new Error('Auth request not ready. Try again in a moment.');
+ }
+ await promptAsync();
+ } catch (e) {
+ setError(e);
+ throw e;
+ }
+ }, [request, promptAsync]);
+
+ const signOut = useCallback(async () => {
+ await SecureStore.deleteItemAsync(STORAGE_KEY);
+ setSession(null);
+ }, []);
+
+ const value = useMemo(
+ () => ({
+ user: session?.user ?? null,
+ tokens: session?.tokens ?? null,
+ loading,
+ error,
+ signIn,
+ signOut,
+ }),
+ [session, loading, error, signIn, signOut],
+ );
+
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
+}
+
+export function useAuth() {
+ return useContext(AuthContext);
+}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/lib/env.js.html b/coverage/lcov-report/lib/env.js.html
new file mode 100644
index 0000000..58ee73f
--- /dev/null
+++ b/coverage/lcov-report/lib/env.js.html
@@ -0,0 +1,175 @@
+
+
+
+
+
+ Code coverage report for lib/env.js
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 66.66%
+ Statements
+ 4/6
+
+
+
+
+ 0%
+ Branches
+ 0/2
+
+
+
+
+ 0%
+ Functions
+ 0/1
+
+
+
+
+ 66.66%
+ Lines
+ 4/6
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+
+
+
+
+
+1x
+1x
+1x
+
+1x
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ // Read-once env config for Google OAuth.
+//
+// Expo SDK 52 inlines `EXPO_PUBLIC_*` vars from `.env` into the bundle at build time,
+// so `process.env.EXPO_PUBLIC_FOO` works on device. Missing vars here surface a clear
+// error at the point of use (sign-in), not a cryptic OAuth failure later.
+
+const WEB = process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID;
+const IOS = process.env.EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID;
+const ANDROID = process.env.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID;
+
+export const googleClientIds = {
+ webClientId: WEB,
+ iosClientId: IOS,
+ androidClientId: ANDROID,
+};
+
+/**
+ * Throw if the web client ID is missing. The web client ID is the minimum
+ * required for Expo AuthSession proxy dev flow; native IDs are only needed
+ * for standalone production builds.
+ */
+export function assertGoogleEnv( ) {
+ if (!WEB) {
+ throw new Error(
+ 'Missing EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID. ' +
+ 'Create OAuth client IDs in Google Cloud Console and add them to .env. ' +
+ 'See .env.example and README "Auth" section.',
+ );
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/lib/index.html b/coverage/lcov-report/lib/index.html
new file mode 100644
index 0000000..a026750
--- /dev/null
+++ b/coverage/lcov-report/lib/index.html
@@ -0,0 +1,131 @@
+
+
+
+
+
+ Code coverage report for lib
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 78.4%
+ Statements
+ 69/88
+
+
+
+
+ 64.06%
+ Branches
+ 41/64
+
+
+
+
+ 70.58%
+ Functions
+ 12/17
+
+
+
+
+ 84.41%
+ Lines
+ 65/77
+
+
+
+
+
+ Press n or j to go to the next uncovered block, b , p or k for the previous block.
+
+
+
+ Filter:
+
+
+
+
+
+
+
+
+
+ File
+
+ Statements
+
+ Branches
+
+ Functions
+
+ Lines
+
+
+
+
+ auth-context.js
+
+
+
+ 79.26%
+ 65/82
+ 66.12%
+ 41/62
+ 75%
+ 12/16
+ 85.91%
+ 61/71
+
+
+
+ env.js
+
+
+
+ 66.66%
+ 4/6
+ 0%
+ 0/2
+ 0%
+ 0/1
+ 66.66%
+ 4/6
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/coverage/lcov-report/prettify.css b/coverage/lcov-report/prettify.css
new file mode 100644
index 0000000..b317a7c
--- /dev/null
+++ b/coverage/lcov-report/prettify.css
@@ -0,0 +1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
diff --git a/coverage/lcov-report/prettify.js b/coverage/lcov-report/prettify.js
new file mode 100644
index 0000000..b322523
--- /dev/null
+++ b/coverage/lcov-report/prettify.js
@@ -0,0 +1,2 @@
+/* eslint-disable */
+window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^