From 39dd37f89cfa23b184ce97b837e205df2fef600d Mon Sep 17 00:00:00 2001 From: Offirmo Date: Wed, 22 May 2024 20:35:17 +1000 Subject: [PATCH] +++ --- .../src/normalizers/url/index.ts | 8 + .../3-advanced/utils--async/src/index.ts | 1 + .../lists/senior-dev/tech-bites--concepts.md | 4 +- .../lists/senior-dev/tech-bites--gamedev.md | 2 + .../lists/senior-dev/tech-bites--process.md | 2 + .../tech-bites--product-and-strategy.md | 14 ++ .../lists/senior-dev/tech-bites--web.md | 9 +- .../lists/serious-memes/mental-models.md | 1 + .../src/snippets/html/react-root.ts | 5 +- .../src/generate--http-headers/NOTES.md | 3 + .../src/generate--src/index.ts | 171 +++++++++++------- .../5-incubator/active/pwa-debugger/.parcelrc | 3 + .../active/pwa-debugger/src/app/consts.ts | 1 + .../pwa-debugger/src/app/controllers/flux.tsx | 21 +++ .../src/app/controllers/state--app.tsx | 21 +++ .../active/pwa-debugger/src/app/index.ts | 35 ++++ .../pwa-debugger/src/app/init/00-security.ts | 57 ++++++ .../pwa-debugger/src/app/init/01-logger.ts | 11 ++ .../pwa-debugger/src/app/init/02-sxc.ts | 42 +++++ .../pwa-debugger/src/app/init/03-errors.ts | 45 +++++ .../pwa-debugger/src/app/init/10-loader.tsx | 15 ++ .../pwa-debugger/src/app/init/11-flux.tsx | 15 ++ .../pwa-debugger/src/app/init/12-view.tsx | 15 ++ .../pwa-debugger/src/app/init/20-auth.ts | 13 ++ .../pwa-debugger/src/app/init/30-analytics.ts | 13 ++ .../pwa-debugger/src/app/services/auth.ts | 21 +++ .../pwa-debugger/src/app/services/channel.ts | 44 +++++ .../pwa-debugger/src/app/services/loader.ts | 21 +++ .../pwa-debugger/src/app/services/logger.ts | 17 ++ .../pwa-debugger/src/app/view/index.tsx | 21 +++ .../src/~~gen/generate-entry-points/index.mts | 5 + stack--2022/9-rpg/NOTES--content.md | 1 + stack--2022/9-rpg/NOTES--lifestages.md | 16 ++ 33 files changed, 602 insertions(+), 71 deletions(-) create mode 100644 stack--2022/5-incubator/active/pwa-debugger/.parcelrc create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/consts.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/flux.tsx create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/state--app.tsx create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/index.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/00-security.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/01-logger.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/02-sxc.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/03-errors.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/10-loader.tsx create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/11-flux.tsx create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/12-view.tsx create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/20-auth.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/init/30-analytics.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/services/auth.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/services/channel.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/services/loader.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/services/logger.ts create mode 100644 stack--2022/5-incubator/active/pwa-debugger/src/app/view/index.tsx create mode 100644 stack--2022/9-rpg/NOTES--lifestages.md diff --git a/stack--2022/3-advanced/normalize-string/src/normalizers/url/index.ts b/stack--2022/3-advanced/normalize-string/src/normalizers/url/index.ts index 00afd38e..aaa5757a 100644 --- a/stack--2022/3-advanced/normalize-string/src/normalizers/url/index.ts +++ b/stack--2022/3-advanced/normalize-string/src/normalizers/url/index.ts @@ -10,6 +10,14 @@ import { normalizeꓽemailⵧreasonable, hasꓽemail_structure } from '../email/ // https://en.wikipedia.org/wiki/URL function _normalizeⵧschemeꘌhttpₓ(url: string): string { + try { + // TODO one day URL.canParse + new URL(url) + } + catch(e) { + throw new Error(`Invalid URL!`) + } + let [scheme, ...rest] = url.split(':') scheme = scheme.toLowerCase() diff --git a/stack--2022/3-advanced/utils--async/src/index.ts b/stack--2022/3-advanced/utils--async/src/index.ts index e831afba..8e76de98 100644 --- a/stack--2022/3-advanced/utils--async/src/index.ts +++ b/stack--2022/3-advanced/utils--async/src/index.ts @@ -1,3 +1,4 @@ export * from './awaitable.js' export * from './ponyfills.js' export * from './semantic.js' +export * from './promises.js' diff --git a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--concepts.md b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--concepts.md index 98cf4f04..b66272ab 100644 --- a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--concepts.md +++ b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--concepts.md @@ -3,9 +3,9 @@ +++ https://martinfowler.com/ +++ https://www.hackterms.com/about/all +++ https://www.linfo.org/main_index.html -[ ] https://zknill.io/posts/every-programmer-should-know/ 12 factors https://12factor.net/ 97 Things Every Programmer Should Know +[ ] https://zknill.io/posts/every-programmer-should-know/ abstraction https://www.merrickchristensen.com/articles/abstraction/ allocation annotation @@ -167,6 +167,7 @@ IDEALS -- 6 Single responsibility IDEALS = principles for microservice design If it ain't broke, don't fix it IIFE +naming https://ntietz.com/blog/when-to-use-cute-names-or-descriptive-names/ immutability Interface segregation principle = instead of a class interface with all possible methods clients might need, there should be separate interfaces catering to the specific needs of each type of client https://en.wikipedia.org/wiki/Interface_segregation_principle Joel test @@ -305,6 +306,7 @@ TDZ tech debt -- Accidental - e.g. bugs due to human error that unknowingly increases the cost of future work. tech debt -- Deliberate - e.g. optimising for short term delivery, knowing it increases the cost of future work. tech debt -- Incidental - e.g. organic changes in complexity over time that increases the cost of future work. +temporary solutions https://80.lv/articles/this-30-year-old-windows-feature-was-created-as-a-temporary-solution/ the room https://lethain.com/getting-in-the-room/ this thread diff --git a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--gamedev.md b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--gamedev.md index ed4db7b5..bf60f47c 100644 --- a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--gamedev.md +++ b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--gamedev.md @@ -136,8 +136,10 @@ transmog turtling twinking UI -- HUD +UI -- HUD https://en.wikipedia.org/wiki/HUD_(video_games) UI -- minimap UI -- radar +UI https://news.blizzard.com/en-us/world-of-warcraft/23837944/get-into-the-grid-of-things-with-the-updated-ui-and-hud UX -- title screen woodwalking zone diff --git a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--process.md b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--process.md index acee67ab..dd9c3815 100644 --- a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--process.md +++ b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--process.md @@ -25,8 +25,10 @@ documentation -- divio system -- 04 Explanations (or discussions) = understandin documentation -- divio system = https://documentation.divio.com/ https://nick.groenen.me/posts/the-4-types-of-technical-documentation/ Every piece of business finance I could come up with after 10 years as a CFO https://twitter.com/KurtisHanni/status/1560986912613072899 exec summary https://lethain.com/present-to-executives/ +fix broken windows https://frontendatscale.com/issues/22/ full service ownership https://nick.groenen.me/notes/full-service-ownership/#full-service-ownership Headline driven development (Amazon) https://www.spakhm.com/headline-development +How Might We? (HMW) = technique used to frame design challenges by formulating questions that focus on the desired outcome, are broad, and are phrased positively to encourage creativity and generate ideas Jobs to be Done (JTBD) https://en.wikipedia.org/wiki/Jobs-to-be-done_theory Kanban https://en.wikipedia.org/wiki/Kanban_(development) merge queues https://engineering.shopify.com/blogs/engineering/successfully-merging-work-1000-developers diff --git a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md index c639c6b0..550de706 100644 --- a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md +++ b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md @@ -1,8 +1,12 @@ 6 Reasons Users Hate Your New Feature https://www.slicedbreaddesign.com/blog/6-reasons-users-hate-your-new-feature +[ ] personal MBA active users -- daily (DAU) https://www.socialmediatoday.com/news/look-comparative-mau-and-dau-stats-major-social-apps/694563/ active users -- monthly (MAU) https://www.socialmediatoday.com/news/look-comparative-mau-and-dau-stats-major-social-apps/694563/ +aha moment +Annual Recurring Revenue (ARR) average revenue per user (ARPU) churn +customer segmentation = Strategic, Enterprise, Mid-Market, SMB earliest -- 01 feedback-able product (EFP) earliest -- 02 testable product = first release that customers can actually do something with earliest -- 03 usable product = first release that early adopters will actually use, willingly @@ -14,6 +18,7 @@ earliest https://every.to/p/the-most-advanced-yet-acceptable-products-win Embrace, Extend, and Extinguish https://en.wikipedia.org/wiki/Embrace,_extend,_and_extinguish enshittification = first, they are good to their users; then they abuse their users to make things better for their business customers; finally, they abuse those business customers to claw back all the value for themselves. Then, they die. https://en.wikipedia.org/wiki/Enshittification feature creep +financing cycle https://en.wikipedia.org/wiki/Seed_money funnel illegal practices -- monopoly illegal practices -- payola https://en.wikipedia.org/wiki/Payola @@ -24,6 +29,7 @@ illegal practices -- price skimming incentives -- perverse incentive https://en.wikipedia.org/wiki/Perverse_incentive land grab lock-in +magic moment -> habit moment moat https://www.semianalysis.com/p/google-we-have-no-moat-and-neither Most Advanced Yet Acceptable (MAYA) UX concept network effect @@ -33,8 +39,16 @@ path to commercial viability = Ex. X moonshots "proved much longer and riskier t principle -- end-to-end principle = fundamental principle of the Internet in which the role of a network is to reliably deliver data from willing senders to willing receivers principle -- right of exit = users of a platform can easily go elsewhere if they are dissatisfied with it. This requires interoperability, countering the network effects procurement +product market fit (PMF) https://www.sequoiacap.com/article/pmf-framework/ +product market fit -- Future Vision +product market fit -- Hair on Fire +product market fit -- Hard Fact +real-world context https://contextsdk.com/insights#product strategy https://stratechery.com/ switching barriers switching cost +System of Work = A connected portfolio of products with clear and discrete value propositions; built on a common platform and data model; can help implement an opinionated set of practices for effective teamwork across all kinds of teams +total addressable market (TAM) try/test/graduate +UXR vendor lock-in diff --git a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--web.md b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--web.md index 1d1a7b56..bb75b309 100644 --- a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--web.md +++ b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--web.md @@ -1,5 +1,7 @@ +++ https://developer.mozilla.org/en-US/curriculum/ [ ] +++ https://dev.to/anze_kop1tar/acronyms-you-should-know-when-going-to-a-job-interview-369l +[ ] security https://portswigger.net/web-security/all-topics +[ ] security https://xsleaks.dev/ [ ] TODO shearing layers https://en.wikipedia.org/wiki/Shearing_layers APIs web appli web @@ -73,15 +75,18 @@ realtime -- patterns -- Push biz events realtime -- patterns -- Push state realtime -- patterns -- Push state diff realtime updates https://zknill.io/posts/how-to-adopt-realtime/ +security -- clickjacking https://portswigger.net/web-security/clickjacking +security -- cross-site leaks https://xsleaks.dev/ testing https://www.testingjavascript.com/ UI -- Application posture = sovereign, transient, background, auxiliary https://en.wikipedia.org/wiki/Application_posture UI -- aria https://www.w3.org/WAI/ARIA/apg/ +UI -- chrome UI -- contrast ratio https://www.siegemedia.com/contrast-ratio -UI -- game -- HUD https://en.wikipedia.org/wiki/HUD_(video_games) -UI -- game https://news.blizzard.com/en-us/world-of-warcraft/23837944/get-into-the-grid-of-things-with-the-updated-ui-and-hud +UI -- HUD UI -- modals -- sheets = ~semi-modal https://en.wikipedia.org/wiki/Modal_window#Modal_sheets_in_Mac_OS_X UI -- modals https://en.wikipedia.org/wiki/Modal_window UI -- modes https://en.wikipedia.org/wiki/User_interface#Modalities_and_modes +UI -- views User Agent Interface (UA) https://www.bram.us/2021/07/08/the-large-small-and-dynamic-viewports/#large-viewport UX -- Above the fold = is the area of a webpage that fits in a browser window without a user having to scroll down. This is the content that is first seen by the user and often dictates whether they’ll continue reading the webpage. UX -- honeycomb = useful + usable + findable + desirable + accessible + credible = valuable https://en.wikipedia.org/wiki/User_interface#A_model_of_design_criteria:_User_Experience_Honeycomb diff --git a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/serious-memes/mental-models.md b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/serious-memes/mental-models.md index 4bf4449e..dc1b9b48 100644 --- a/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/serious-memes/mental-models.md +++ b/stack--2022/5-incubator/active--no-pkg/bite-sized/lists/serious-memes/mental-models.md @@ -24,6 +24,7 @@ Baptists and Bootleggers https://a16z.com/ai-will-save-the-world/ base rate: The average outcome for an event over time. They’re like batting averages for life, and they work best with big sample sizes. For example, if you’re starting a business, avoid the restaurant business where margins are low and competition is high. be prepared bee browsing +broken window theory bike-shed Effect: A group of people working on a project will fight over the most trivial ideas. They’ll ignore what’s complicated. They’ll focus too much on easy-to-understand ideas at the expense of important, but hard to talk about ideas. For example, instead of approving plans for a complicated spaceship, the team would argue over the color of the astronaut’s uniforms. bonne et mauvaises cartes brand -- 6 what's? = what we stand for, what we believe in, what people we seek to engage, what distinguishes us, what we offer, what we say and show https://hbr.org/2014/06/start-ups-need-a-minimum-viable-brand diff --git a/stack--2022/5-incubator/active/generator--html/src/snippets/html/react-root.ts b/stack--2022/5-incubator/active/generator--html/src/snippets/html/react-root.ts index 41d8ff3e..96fb7c4b 100644 --- a/stack--2022/5-incubator/active/generator--html/src/snippets/html/react-root.ts +++ b/stack--2022/5-incubator/active/generator--html/src/snippets/html/react-root.ts @@ -19,9 +19,8 @@ function generate(spec: Immutable): Html‿str {

${getꓽtitleⵧpage(spec, 'Loading…')}

Loading… diff --git a/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--http-headers/NOTES.md b/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--http-headers/NOTES.md index d0788457..a442d72a 100644 --- a/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--http-headers/NOTES.md +++ b/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--http-headers/NOTES.md @@ -1,2 +1,5 @@ TODO CSP + +good reference! +https://web.dev/articles/security-headers diff --git a/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--src/index.ts b/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--src/index.ts index b1a601ca..eca7b95c 100644 --- a/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--src/index.ts +++ b/stack--2022/5-incubator/active/generator--website-entry-points/src/generate--src/index.ts @@ -17,26 +17,24 @@ const CODE_TEMPLATEⵧGENERIC = ` import assert from 'tiny-invariant' import { Immutable } from '@offirmo-private/ts-types' -import { } from './types.js' +//import { } from './types.js' ///////////////////////////////////////////////// -function create(): Immutable<> { -function getꓽXYZⵧfoo‿v2(): void {} -/* +function some_stuff(): Immutable { +/*function getꓽXYZⵧfoo‿v2(): void {} ↆfoo ⵧ fetch ೱfoo ⵧ promise ϟaꘌb notᝍbadₓasⳇwell‿noǃ bar𝝣fooǃfoo𖾚fooᐧbar */ + throw new Error('NIMP!') } ///////////////////////////////////////////////// -export { - ... -} +export default some_stuff `.trim() const CODE_TEMPLATEⵧSERVICESⳇLOGGER = ` @@ -59,7 +57,64 @@ const logger = getLogger({ export default logger `.trim() -const CODE_TEMPLATEⵧSERVICESⳇINITⵧCRITICALⳇLOGGER = ` +function genꓽCODE_TEMPLATEⵧSERVICESⳇCHANNEL(spec: Immutable): string { + const URLⵧCANONICAL‿str = spec.urlⵧcanonical + const URLⵧCANONICAL‿url= new URL(URLⵧCANONICAL‿str) + + return ` +//import { is_loaded_from_cordova } from './cordova' TODO + +import { LIB } from '../consts.ts' + +const URLⵧCANONICAL = '${spec.urlⵧcanonical}' + +///////////////////////////////////////////////// + +function isꓽprod() { + const URLⵧCANONICAL‿obj = new URL(URLⵧCANONICAL) + + if (window.location.protocol !== 'https:') + return false + + if (window.location.port) + return false + + if (window.location.hostname !== URLⵧCANONICAL‿obj.hostname) + return false + + //is_loaded_from_cordova() TODO + return true +} + +function isꓽstaging() { + if (window.location.protocol !== 'https:') + return false + + if (window.location.port) + return false + + // TODO add more + if (window.location.hostname.endsWith('netlify.app')) + return true + + return false +} + +const CHANNEL = isꓽprod() + ? 'prod' + : isꓽstaging() + ? 'staging' + : 'dev' + +///////////////////////////////////////////////// + +export { + CHANNEL, +} +`.trim() +} + +const CODE_TEMPLATEⳇINITⳇLOGGER = ` import logger from '../logger.ts' ///////////////////////////////////////////////// @@ -73,7 +128,7 @@ async function init(): Promise { export default init `.trim() -const CODE_TEMPLATEⵧSERVICESⳇINITⵧCRITICALⳇSXC = ` +const CODE_TEMPLATEⳇINITⳇSXC = ` import { getRootSXC, decorateWithDetectedEnv } from '@offirmo-private/soft-execution-context--browser' import { LIB } from '../../consts.ts' @@ -118,7 +173,7 @@ async function init(): Promise { export default init `.trim() -const CODE_TEMPLATEⵧSERVICESⳇINITⵧCRITICALⳇERRORS = ` +const CODE_TEMPLATEⳇINITⳇERRORS = ` import { getRootSXC } from '@offirmo-private/soft-execution-context' import { listenToErrorEvents, listenToUnhandledRejections } from '@offirmo-private/soft-execution-context--browser' @@ -167,25 +222,34 @@ async function init(): Promise { export default init `.trim() +const CODE_TEMPLATEⳇINITⳇGENERIC = ` +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +import { CHANNEL } from '../services/channel.ts' -function generate(spec: Immutable): EntryPoints { - return { - './app/consts.ts': ` ///////////////////////////////////////////////// -const LIB = '${getꓽtitleⵧlib(spec)}' +async function init(): Promise { + const rootSXC = getRootSXC() -///////////////////////////////////////////////// + rootSXC.xTry('init', ({logger, SXC}) => { -export { - LIB, + }) } -`.trimStart(), + +///////////////////////////////////////////////// + +export default init +`.trim() + +function generate(spec: Immutable): EntryPoints { + return { + './app/consts.ts': `export const LIB = '${getꓽtitleⵧlib(spec)}'`, './app/index.ts': ` -import { asap_but_out_of_immediate_execution } from '@offirmo-private/async-utils' -import { VERSION, BUILD_DATE } from '../build.ts' +import { asap_but_out_of_immediate_execution, forArray } from '@offirmo-private/async-utils' +import { VERSION, BUILD_DATE } from '../entry-points/build.ts' -//import { CHANNEL } from './services/channel' +import { CHANNEL } from './services/channel' ///////////////////////////////////////////////// @@ -203,40 +267,14 @@ asap_but_out_of_immediate_execution(async () => { const logger = (await import('./services/logger.ts')).default // order is important! Timing is non-trivial! - const initⵧservicesⵧcritical = await import('./services/init--critical/*.ts') - await Object.keys(initⵧservicesⵧcritical).sort().reduce(async (acc, key) => { - await acc - logger.group(\`services/initⵧcritical "\${key}"\`) - logger.trace(\`services/initⵧcritical "\${key}": import…\`) - const init_fn = (await initⵧservicesⵧcritical[key]()).default - logger.trace(\`services/initⵧcritical "\${key}": exec…\`) - await init_fn() - logger.trace(\`services/initⵧcritical "\${key}": done✅\`) - logger.groupEnd() - }, Promise.resolve()) - - // order is important! Timing is non-trivial! - const initⵧview = await import('./view/init/*.tsx') - await Object.keys(initⵧview).sort().reduce(async (acc, key) => { - await acc - logger.group(\`services/view "\${key}"\`) - logger.trace(\`services/view "\${key}": import…\`) - const init_fn = (await initⵧview[key]()).default - logger.trace(\`services/view "\${key}": exec…\`) + const init = await import('./init/*.ts') + await forArray(Object.keys(init).sort()).executeSequentially((key) => { + logger.group(\`init/"\${key}"\`) + logger.trace(\`init/"\${key}": import…\`) + const init_fn = (await init[key]()).default + logger.trace(\`init/"\${key}": exec…\`) await init_fn() - logger.trace(\`services/view "\${key}": done✅\`) - logger.groupEnd() - }, Promise.resolve()) - - const initⵧservicesⵧnoncritical = await import('./services/init--noncritical/*.ts') - await Object.keys(initⵧservicesⵧnoncritical).sort().reduce(async (acc, key) => { - await acc - logger.group(\`services/initⵧnoncritical "\${key}"\`) - logger.trace(\`services/initⵧnoncritical "\${key}": import…\`) - const init_fn = (await initⵧservicesⵧnoncritical[key]()).default - logger.trace(\`services/initⵧnoncritical "\${key}": exec…\`) - await init_fn() - logger.trace(\`services/initⵧnoncritical "\${key}": done✅\`) + logger.trace(\`init/"\${key}": done ✅\`) logger.groupEnd() }, Promise.resolve()) }) @@ -244,26 +282,29 @@ asap_but_out_of_immediate_execution(async () => { // service layer // ~syncing view with external data sources - './app/services/init--critical/00-logger.ts': CODE_TEMPLATEⵧSERVICESⳇINITⵧCRITICALⳇLOGGER, - './app/services/init--critical/01-sxc.ts': CODE_TEMPLATEⵧSERVICESⳇINITⵧCRITICALⳇSXC, - './app/services/init--critical/10-errors.ts': CODE_TEMPLATEⵧSERVICESⳇINITⵧCRITICALⳇERRORS, - './app/services/init--critical/11-security.ts': CODE_TEMPLATEⵧGENERIC, - - './app/services/init--noncritical/10-analytics.ts': CODE_TEMPLATEⵧGENERIC, - './app/services/init--noncritical/10-auth.ts': CODE_TEMPLATEⵧGENERIC, - './app/services/auth.ts': CODE_TEMPLATEⵧGENERIC, - './app/services/channel.ts': CODE_TEMPLATEⵧGENERIC, + './app/services/channel.ts': genꓽCODE_TEMPLATEⵧSERVICESⳇCHANNEL(spec), './app/services/loader.ts': CODE_TEMPLATEⵧGENERIC, './app/services/logger.ts': CODE_TEMPLATEⵧSERVICESⳇLOGGER, // controllers // ~shared state and stateful logic - './app/controllers/context.tsx': CODE_TEMPLATEⵧGENERIC, + './app/controllers/state--app.tsx': CODE_TEMPLATEⵧGENERIC, + './app/controllers/flux.tsx': CODE_TEMPLATEⵧGENERIC, // view - './app/view/init/react.tsx': CODE_TEMPLATEⵧGENERIC, './app/view/index.tsx': CODE_TEMPLATEⵧGENERIC, + + // init + './app/init/00-logger.ts': CODE_TEMPLATEⳇINITⳇLOGGER, + './app/init/01-security.ts': CODE_TEMPLATEⳇINITⳇGENERIC, + './app/init/02-sxc.ts': CODE_TEMPLATEⳇINITⳇSXC, + './app/init/03-errors.ts': CODE_TEMPLATEⳇINITⳇERRORS, + './app/init/10-loader.tsx': CODE_TEMPLATEⳇINITⳇGENERIC, + './app/init/11-flux.tsx': CODE_TEMPLATEⳇINITⳇGENERIC, + './app/init/12-view.tsx': CODE_TEMPLATEⳇINITⳇGENERIC, + './app/init/20-auth.ts': CODE_TEMPLATEⳇINITⳇGENERIC, + './app/init/30-analytics.ts': CODE_TEMPLATEⳇINITⳇGENERIC, } } diff --git a/stack--2022/5-incubator/active/pwa-debugger/.parcelrc b/stack--2022/5-incubator/active/pwa-debugger/.parcelrc new file mode 100644 index 00000000..53ccb509 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/.parcelrc @@ -0,0 +1,3 @@ +{ + "extends": "@offirmo-private/parcel-config--default" +} diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/consts.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/consts.ts new file mode 100644 index 00000000..ef496d36 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/consts.ts @@ -0,0 +1 @@ +export const LIB = 'pwa-debugger' diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/flux.tsx b/stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/flux.tsx new file mode 100644 index 00000000..26976e4f --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/flux.tsx @@ -0,0 +1,21 @@ +import assert from 'tiny-invariant' +import { Immutable } from '@offirmo-private/ts-types' + +//import { } from './types.js' + +///////////////////////////////////////////////// + +function some_stuff(): Immutable { +/*function getꓽXYZⵧfoo‿v2(): void {} +ↆfoo ⵧ fetch +ೱfoo ⵧ promise +ϟaꘌb +notᝍbadₓasⳇwell‿noǃ +bar𝝣fooǃfoo𖾚fooᐧbar + */ + throw new Error('NIMP!') +} + +///////////////////////////////////////////////// + +export default some_stuff \ No newline at end of file diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/state--app.tsx b/stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/state--app.tsx new file mode 100644 index 00000000..26976e4f --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/controllers/state--app.tsx @@ -0,0 +1,21 @@ +import assert from 'tiny-invariant' +import { Immutable } from '@offirmo-private/ts-types' + +//import { } from './types.js' + +///////////////////////////////////////////////// + +function some_stuff(): Immutable { +/*function getꓽXYZⵧfoo‿v2(): void {} +ↆfoo ⵧ fetch +ೱfoo ⵧ promise +ϟaꘌb +notᝍbadₓasⳇwell‿noǃ +bar𝝣fooǃfoo𖾚fooᐧbar + */ + throw new Error('NIMP!') +} + +///////////////////////////////////////////////// + +export default some_stuff \ No newline at end of file diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/index.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/index.ts new file mode 100644 index 00000000..62e6b603 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/index.ts @@ -0,0 +1,35 @@ +import { asap_but_out_of_immediate_execution, forArray } from '@offirmo-private/async-utils' +import { VERSION, BUILD_DATE } from '../entry-points/build.ts' +import './init/00-security.ts' + +import { CHANNEL } from './services/channel' + +///////////////////////////////////////////////// + +console.info( + `%cWelcome to %cPWA debugger %cv${VERSION}%c${BUILD_DATE}`, + 'font-weight: bold;', + 'border-radius: 1em; padding: .1em .5em; margin-inline-end: 1ch; background-color: hsl(337, 16%, 28%); color: hsl(42, 100%, 87%); font-weight: bold;', + 'border-radius: 1em; padding: .1em .5em; margin-inline-end: 1ch; background-color: darkgrey; color: black; font-weight: bold;', + 'border-radius: 1em; padding: .1em .5em; margin-inline-end: 1ch; background-color: darkgrey; color: black;', +) + +///////////////////////////////////////////////// + +asap_but_out_of_immediate_execution(async () => { + console.log('%c——————— end of immediate, synchronous, non-import code. ———————', 'font-weight: bold;') + + const logger = (await import('./services/logger.ts')).default + + // order is important! Timing is non-trivial! + const init = await import('./init/*.ts') + await forArray(Object.keys(init).sort()).executeSequentially(async (key) => { + logger.group(`init/"${key}"`) + logger.trace(`init/"${key}": import…`) + const init_fn = (await init[key]()).default + logger.trace(`init/"${key}": exec…`) + await init_fn() + logger.trace(`init/"${key}": done ✅`) + logger.groupEnd() + }) +}) diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/00-security.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/00-security.ts new file mode 100644 index 00000000..49f6f0b7 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/00-security.ts @@ -0,0 +1,57 @@ +/** Immediate security initializations. + * This code will be executed immediately when the app starts. + * + * We can't ensure full security, but we can try to mitigate some risks. + * https://owasp.org/www-project-top-ten/ + * - We assume our own code can be compromised (ex. supply chain) + * - We assume the opener of our page may have injected some js https://krausefx.com//blog/ios-privacy-instagram-and-facebook-can-track-anything-you-do-on-any-website-in-their-in-app-browser + */ +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +///////////////////////////////////////////////// + +// add immediate security actions here +function initⵧimmediate(): void { + // TODO should happen synchronously inside the HTML! + console.log('%cEnforcing app security…', 'font-style: oblique;') + // protect against prototype pollution + // is that really useful? + // https://www.synopsys.com/blogs/software-security/javascript-security-best-practices.html#6 + Object.freeze(Object.prototype) + + // TODO strip global scope and APIs +} + + +// add logs or later security actions here +async function initⵧdeferred() { + getRootSXC().xTry('init:SXC', ({ logger }) => { + // ensure we have a CSP + const allowsꓽunsafe_evals = (() => { + // https://stackoverflow.com/a/27399739/587407 + try { new Function(''); return true } + catch (e) { return false } + })() + if(allowsꓽunsafe_evals) { + // this is the first thing a CSP disables... + logger.error('Content Security Policy is missing! Consider this app unsafe!') + } + + const isꓽiframe = window.self !== window.top + if(isꓽiframe) { + // strictly speaking the web is composable and there is nothing wrong with being an iframe + // however there are a few attacks... + // https://web.dev/articles/security-headers#xfo + logger.warn('Running in an iframe, be cautious!!') + // since we don't have any iframe-specific feature, let's cut off the connection + window.parent = window + window.top = window + } + }) +} + +///////////////////////////////////////////////// + +initⵧimmediate() + +export default initⵧdeferred diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/01-logger.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/01-logger.ts new file mode 100644 index 00000000..81a63455 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/01-logger.ts @@ -0,0 +1,11 @@ +import logger from '../services/logger.ts' + +///////////////////////////////////////////////// + +async function init(): Promise { + console.log(`🗂 Logger up with level "${logger.getLevel()}". Reminder to check your dev tools log level!`) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/02-sxc.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/02-sxc.ts new file mode 100644 index 00000000..9f4ad1a5 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/02-sxc.ts @@ -0,0 +1,42 @@ +import { getRootSXC, decorateWithDetectedEnv } from '@offirmo-private/soft-execution-context--browser' + +import { LIB } from '../consts.ts' +import { VERSION } from '../../entry-points/build.ts' +import { CHANNEL } from '../services/channel.ts' +import logger from '../services/logger.ts' + +///////////////////////////////////////////////// + +async function init(): Promise { + const rootSXC = getRootSXC() + + decorateWithDetectedEnv(rootSXC) + + rootSXC.setLogicalStack({ module: LIB }) + + rootSXC.injectDependencies({ + logger, + CHANNEL, + VERSION, + }) + + rootSXC.setAnalyticsAndErrorDetails({ + VERSION, + CHANNEL, + }) + + rootSXC.xTry('init:SXC', ({ logger, SXC }) => { + logger.debug('┌ Root SXC is now decorated with a logger ✔') + logger.debug('├ Root SXC is now decorated with env infos ✔', SXC.getAnalyticsDetails()) + logger.debug('└► Root Soft Execution Context initialized ✔', rootSXC) + }) + + const { ENV } = rootSXC.getInjectedDependencies() + if (ENV !== process.env.NODE_ENV) { + logger.error('ENV detection mismatch!', { 'SXC.ENV': ENV, 'process.env.NODE_ENV': process.env.NODE_ENV }) + } +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/03-errors.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/03-errors.ts new file mode 100644 index 00000000..5097d01f --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/03-errors.ts @@ -0,0 +1,45 @@ +import { getRootSXC } from '@offirmo-private/soft-execution-context' +import { listenToErrorEvents, listenToUnhandledRejections } from '@offirmo-private/soft-execution-context--browser' + +import { CHANNEL } from '../services/channel.ts' + +///////////////////////////////////////////////// + +const STYLES = 'padding: .5em; background-color: red; color: white; font-weight: bold;' + +async function init(): Promise { + const rootSXC = getRootSXC() + + rootSXC.emitter.on('final-error', function onFinalError({ SXC, err }) { + try { + // this code must be super extra safe!!! + // don't even use the advanced logger! + + console.group('%cSXC "final-error" event!', STYLES) + + if (CHANNEL === 'dev') { + console.error('%c↑ error! (no report since dev)', STYLES, { SXC, err }) + return + } + + //console.log('(this error will be reported)') + // TODO integrate with Sentry! + + console.groupEnd() + } catch (err) { + console.log(`%c RECURSIVE CRASH!!! SXC ERROR HANDLING CAN ABSOLUTELY NOT CRASH!!! FIX THIS!!!`, STYLES) + console.log(err) + } + }) + + listenToErrorEvents() + listenToUnhandledRejections() + + rootSXC.xTry('init:SXC', ({ logger, SXC }) => { + logger.debug('Root SXC is now decorated with error details ✔', SXC.getErrorDetails()) + }) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/10-loader.tsx b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/10-loader.tsx new file mode 100644 index 00000000..19fe9f84 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/10-loader.tsx @@ -0,0 +1,15 @@ +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +///////////////////////////////////////////////// + +async function init(): Promise { + const rootSXC = getRootSXC() + + rootSXC.xTry('init', ({logger, SXC}) => { + + }) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/11-flux.tsx b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/11-flux.tsx new file mode 100644 index 00000000..19fe9f84 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/11-flux.tsx @@ -0,0 +1,15 @@ +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +///////////////////////////////////////////////// + +async function init(): Promise { + const rootSXC = getRootSXC() + + rootSXC.xTry('init', ({logger, SXC}) => { + + }) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/12-view.tsx b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/12-view.tsx new file mode 100644 index 00000000..19fe9f84 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/12-view.tsx @@ -0,0 +1,15 @@ +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +///////////////////////////////////////////////// + +async function init(): Promise { + const rootSXC = getRootSXC() + + rootSXC.xTry('init', ({logger, SXC}) => { + + }) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/20-auth.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/20-auth.ts new file mode 100644 index 00000000..b4fd4d51 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/20-auth.ts @@ -0,0 +1,13 @@ +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +///////////////////////////////////////////////// + +async function init(): Promise { + const rootSXC = getRootSXC() + + rootSXC.xTry('init', ({ logger, SXC }) => {}) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/init/30-analytics.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/30-analytics.ts new file mode 100644 index 00000000..b4fd4d51 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/init/30-analytics.ts @@ -0,0 +1,13 @@ +import { getRootSXC } from '@offirmo-private/soft-execution-context' + +///////////////////////////////////////////////// + +async function init(): Promise { + const rootSXC = getRootSXC() + + rootSXC.xTry('init', ({ logger, SXC }) => {}) +} + +///////////////////////////////////////////////// + +export default init diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/services/auth.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/auth.ts new file mode 100644 index 00000000..b6173dee --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/auth.ts @@ -0,0 +1,21 @@ +import assert from 'tiny-invariant' +import { Immutable } from '@offirmo-private/ts-types' + +//import { } from './types.js' + +///////////////////////////////////////////////// + +function some_stuff(): Immutable { + /*function getꓽXYZⵧfoo‿v2(): void {} +ↆfoo ⵧ fetch +ೱfoo ⵧ promise +ϟaꘌb +notᝍbadₓasⳇwell‿noǃ +bar𝝣fooǃfoo𖾚fooᐧbar + */ + throw new Error('NIMP!') +} + +///////////////////////////////////////////////// + +export default some_stuff diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/services/channel.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/channel.ts new file mode 100644 index 00000000..c3bad0c5 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/channel.ts @@ -0,0 +1,44 @@ +//import { is_loaded_from_cordova } from './cordova' TODO + +const URLⵧCANONICAL = 'https://www.online-adventur.es/apps/the-boring-rpg/' + +///////////////////////////////////////////////// + +// prod = full security and reliability +// staging = close to prod, usually only difference is the data storage = different from prod +// dev = anything else +const CHANNEL = ((): 'prod' | 'staging' | 'dev' => { + + // first weed out obvious dev cases + if (!window.isSecureContext) return 'dev' + if (window.location.protocol !== 'https:') return 'dev' + if (window.location.port) return 'dev' + + // then detect common "local" dev setups + const isꓽlocalhost = ['localhost', 'example', 'test', 'invalid'].some(domain => window.location.hostname.endsWith(domain)) // https://en.wikipedia.org/wiki/.localhost + if (isꓽlocalhost) return 'dev' + const isꓽtunneledⵧngrok = ['.ngrok-free.app', '.ngrok-free.dev', '.ngrok.app', '.ngrok.dev'].some(domain => window.location.hostname.endsWith(domain)) // https://ngrok.com/blog-post/new-ngrok-domains + if (isꓽtunneledⵧngrok) return 'dev' + const isꓽtunneledⵧcloudflare = ['.cfargotunnel.com'].some(domain => window.location.hostname.endsWith(domain)) // https://developers.cloudflare.com/cloudflare-one/faq/cloudflare-tunnels-faq/ + if (isꓽtunneledⵧcloudflare) return 'dev' + + // then detect common "hosted" locations, which mean staging + if (window.location.hostname.endsWith('.netlify.app')) return 'staging' + if (window.location.hostname.endsWith('.github.io')) return 'staging' + if (window.location.hostname.endsWith('.pages.dev')) return 'staging' // cloudflare + if (window.location.hostname.endsWith('.cloudfront.net')) return 'staging' // AWS + + // finally, does it match the expected canonical URL? + const URLⵧCANONICAL‿obj = new URL(URLⵧCANONICAL) + if (window.location.hostname === URLⵧCANONICAL‿obj.hostname) return 'prod' + + // TODO cordova + // TODO itch.io + + // everything else is unknown = unsafe + return 'dev' +})() + +///////////////////////////////////////////////// + +export { CHANNEL } diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/services/loader.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/loader.ts new file mode 100644 index 00000000..b6173dee --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/loader.ts @@ -0,0 +1,21 @@ +import assert from 'tiny-invariant' +import { Immutable } from '@offirmo-private/ts-types' + +//import { } from './types.js' + +///////////////////////////////////////////////// + +function some_stuff(): Immutable { + /*function getꓽXYZⵧfoo‿v2(): void {} +ↆfoo ⵧ fetch +ೱfoo ⵧ promise +ϟaꘌb +notᝍbadₓasⳇwell‿noǃ +bar𝝣fooǃfoo𖾚fooᐧbar + */ + throw new Error('NIMP!') +} + +///////////////////////////////////////////////// + +export default some_stuff diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/services/logger.ts b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/logger.ts new file mode 100644 index 00000000..05cb4c94 --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/services/logger.ts @@ -0,0 +1,17 @@ +import { getLogger } from '@offirmo/universal-debug-api-browser' + +import { LIB } from '../consts.ts' + +///////////////////////////////////////////////// + +const logger = getLogger({ + name: LIB, + //suggestedLevel: 'error', + //suggestedLevel: 'warn', + //suggestedLevel: 'verbose', + suggestedLevel: 'silly', +}) + +///////////////////////////////////////////////// + +export default logger diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/app/view/index.tsx b/stack--2022/5-incubator/active/pwa-debugger/src/app/view/index.tsx new file mode 100644 index 00000000..26976e4f --- /dev/null +++ b/stack--2022/5-incubator/active/pwa-debugger/src/app/view/index.tsx @@ -0,0 +1,21 @@ +import assert from 'tiny-invariant' +import { Immutable } from '@offirmo-private/ts-types' + +//import { } from './types.js' + +///////////////////////////////////////////////// + +function some_stuff(): Immutable { +/*function getꓽXYZⵧfoo‿v2(): void {} +ↆfoo ⵧ fetch +ೱfoo ⵧ promise +ϟaꘌb +notᝍbadₓasⳇwell‿noǃ +bar𝝣fooǃfoo𖾚fooᐧbar + */ + throw new Error('NIMP!') +} + +///////////////////////////////////////////////// + +export default some_stuff \ No newline at end of file diff --git a/stack--2022/5-incubator/active/pwa-debugger/src/~~gen/generate-entry-points/index.mts b/stack--2022/5-incubator/active/pwa-debugger/src/~~gen/generate-entry-points/index.mts index 460b4a10..63042d48 100644 --- a/stack--2022/5-incubator/active/pwa-debugger/src/~~gen/generate-entry-points/index.mts +++ b/stack--2022/5-incubator/active/pwa-debugger/src/~~gen/generate-entry-points/index.mts @@ -1,4 +1,9 @@ #!/usr/bin/env ts-node + +/** + * yarn refresh--entry-points + */ + import * as path from 'node:path' import { fileURLToPath } from 'node:url' diff --git a/stack--2022/9-rpg/NOTES--content.md b/stack--2022/9-rpg/NOTES--content.md index 1dcb84fa..898d2b9b 100644 --- a/stack--2022/9-rpg/NOTES--content.md +++ b/stack--2022/9-rpg/NOTES--content.md @@ -88,6 +88,7 @@ https://www.personalityresearch.org/bigfive.html She learned that you should never try to intimidate people into telling you the truth. They’ll just put up their guard. Instead, watch for signs of unsuitable nervousness and anxiety, and any pretense of a lack of interest in your questions. The more you talk, the less you’ll learn about the other person – including his or her deceptions. Listen instead. If what the person says sounds rehearsed, you may not be getting the truth. Active listening is the best way to tell truths from lies. +https://en.wikipedia.org/wiki/Miskito_people#Miskito_Gods ## universes diff --git a/stack--2022/9-rpg/NOTES--lifestages.md b/stack--2022/9-rpg/NOTES--lifestages.md new file mode 100644 index 00000000..291f068c --- /dev/null +++ b/stack--2022/9-rpg/NOTES--lifestages.md @@ -0,0 +1,16 @@ + + +## baby + +actions "cry" + +milk +change diaper +sing a song + get really into it and do a 3 voices performance on her own!! +cuddle +wrap / fix wrap +throw +burp +overhear parents +twin bro/sis