diff --git a/stack--current/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md b/stack--current/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md index d5c59152..4db66480 100644 --- a/stack--current/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md +++ b/stack--current/5-incubator/active--no-pkg/bite-sized/lists/senior-dev/tech-bites--product-and-strategy.md @@ -56,8 +56,11 @@ first-mover advantage https://en.wikipedia.org/wiki/First-mover_advantage funnel Go-To-Market (GTM) = plan of an organization, utilizing their outside resources (e.g., sales force and distributors), to deliver their unique value proposition to customers and to achieve a competitive advantage https://en.wikipedia.org/wiki/Go-to-market_strategy growth +++ https://growth.design/ +growth -- cognitive load growth -- concepts https://growth.design/psychology +growth -- friction growth -- labor perception / perceived labor https://growth.design/case-studies/labor-perception-bias +growth -- onboarding -- fatigue illegal practices -- monopoly illegal practices -- payola https://en.wikipedia.org/wiki/Payola illegal practices -- predatory pricing diff --git a/stack--current/5-incubator/active/view--chat/package.json b/stack--current/5-incubator/active/view--chat/package.json index a01f99cf..9d6ddb82 100644 --- a/stack--current/5-incubator/active/view--chat/package.json +++ b/stack--current/5-incubator/active/view--chat/package.json @@ -16,14 +16,24 @@ "tslib": "^2" }, "dependencies": { - "caught": "^0.1", + "@offirmo-private/ts-types": "*", "is-promise": "^4", - "p-progress": "^1" + "p-progress": "^1", + "tiny-invariant": "^1", + "typescript-string-enums":"^1" }, "scripts": { - "cheatsheet": "node doc/cheatsheet.js", - "demo": "echo please use an implementation, ex. node" + "clean": "monorepo-script--clean-package …dist", + + "_build:dev:watch": "monorepo-script--build-typescript-package --watch --module=esm", + "_build:prod": "monorepo-script--build-typescript-package --module=esm", + + "dev": "run-s clean _build:dev:watch", + "test": "./node_modules/.bin/mocha --bail --config ./node_modules/@offirmo/unit-test-toolbox/mocharc.json ./node_modules/@offirmo/unit-test-toolbox/mocha-chai-init-node.mjs 'dist/src.es2023.esm/**/*spec.js'", + "build": "run-s clean _build:prod", + + "demo": "node ./dist/src.es2023.esm/loop/demo.js" }, "devDependencies": { "npm-run-all": "^4", diff --git a/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts b/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts new file mode 100644 index 00000000..86734391 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/__fixtures/primitives--console.ts @@ -0,0 +1,44 @@ +import { type ChatPrimitives } from '../implementation/types.js' + +const CHAT_CONSOLE: ChatPrimitives = { + setup: async () => { + console.log('[ChatPrimitives.setup()]') + }, + + display_message: async ({msg, choices}) => { + console.log('[ChatPrimitives.display_message()]') + + console.log(msg) + if (choices) console.log('Choices:', choices) + }, + + pretend_to_think: async ({duration_ms}) => { throw new Error('NO UI pretend_to_think') }, + + pretend_to_work: async({ + msg_before, + duration_ms, + msg_after, + }) => { + console.log(msg_before) + await new Promise(resolve => setTimeout(resolve, duration_ms)) + console.log(msg_after) + }, + + //read_answer: async () => { throw new Error('NO UI read_answer') }, + + display_task: ({ + msg_before, + progress_promise, + msg_after, + }) => { + throw new Error(`NO UI display_task!`) + }, + + spin_until_resolution: async ({promise}) => promise, + + teardown: async () => { + console.log('[ChatPrimitives.teardown()]') + }, +} + +export default CHAT_CONSOLE diff --git a/stack--current/5-incubator/active/view--chat/doc/demo.js b/stack--current/5-incubator/active/view--chat/src/__fixtures/todo.txt similarity index 97% rename from stack--current/5-incubator/active/view--chat/doc/demo.js rename to stack--current/5-incubator/active/view--chat/src/__fixtures/todo.txt index 459c746b..dbbafd74 100755 --- a/stack--current/5-incubator/active/view--chat/doc/demo.js +++ b/stack--current/5-incubator/active/view--chat/src/__fixtures/todo.txt @@ -1,4 +1,3 @@ -import i_will_handle_rejection_later from 'caught' function* get_next_step1(skip_to_index = 0) { const state = { diff --git a/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts b/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts new file mode 100755 index 00000000..ecdf2904 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/__fixtures/tour.ts @@ -0,0 +1,81 @@ +import { type Step, StepType } from '../types/types.js' +import { type StepsGenerator } from '../loop/types.js' + +export default function* get_next_step(skip_to_index: number = 0) { + const state = { + mode: 'main', + name: undefined, + city: undefined, + } + + const STEPS: Array = [ + + { + type: StepType.perceived_labor, + + msg_before: 'Waking up...', + duration_ms: 2000, + msg_after: 'Awoken!', + }, + + { + type: StepType.progress, + + msg_before: 'Warming up...', + task_promise: (new Promise((resolve, reject) => setTimeout(() => reject(new Error('Demo step 2 rejection!')), 2000))), + msg_after: success => success ? '✔ Ready!' : '❌ Warm up unsuccessful.', + + callback: success => console.log(`[callback called: ${success}]`), + }, + + /* + { + type: 'simple_message', + msg_main: 'Welcome. I’ll have a few questions…', + }, + { + type: 'ask_for_string', + msg_main: 'What’s your name?', + //validator: null, // TODO + msgg_as_user: value => `My name is "${value}".`, + msgg_acknowledge: name => `Thanks for the answer, ${name}!`, + callback: value => { state.name = value }, + }, + { + type: 'ask_for_string', + msg_main: 'What city do you live in?', + msgg_as_user: value => `I live in "${value}".`, + msgg_acknowledge: value => `${value}, a fine city indeed!`, + callback: value => { state.city = value }, + }, + { + type: 'simple_message', + msg_main: 'Please wait for a moment...', + }, + { + type: 'progress', + duration_ms: 1000, + msg_main: 'Calling server...', + }, + { + msg_main: 'Please choose between 1 and 2?', + callback: value => { state.mode = value }, + choices: [ + { + msg_cta: 'Choice 1', + value: 1, + }, + { + msg_cta: 'Choice 2', + value: 2, + }, + ], + }, + { + type: 'simple_message', + msg_main: 'Thanks, good bye.', + },*/ + ] + + yield* STEPS.slice(skip_to_index) +} diff --git a/stack--current/5-incubator/active/view--chat/src/implementation/types.ts b/stack--current/5-incubator/active/view--chat/src/implementation/types.ts new file mode 100644 index 00000000..03e29841 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/implementation/types.ts @@ -0,0 +1,44 @@ +import { Enum } from 'typescript-string-enums' +import { PProgress as PromiseWithProgress } from 'p-progress' +import { type Immutable } from '@offirmo-private/ts-types' + +///////////////////////////////////////////////// + +interface ChatPrimitives { + setup(): Promise + + // core primitives + display_message(p: {msg: ContentType, choices?: ContentType[]}): Promise + + // a staple of chat interfaces + // to be used between steps + pretend_to_think(p: {duration_ms: number}): Promise + + pretend_to_work(p: { + msg_before: ContentType, + duration_ms: number, + msg_after: ContentType, + }): Promise + + //read_answer(step) TODO clarify + + display_task(p: { + msg_before: ContentType, + progress_promise: PromiseWithProgress, + msg_after: ContentType, + }): Promise + + // while we wait for the next step. + // wraps the promise, should return it + // TODO clarify + spin_until_resolution(p: { promise: Promise }): Promise + + // if cleanup is needed + teardown(): Promise +} + +///////////////////////////////////////////////// + +export { + type ChatPrimitives, +} diff --git a/stack--current/5-incubator/active/view--chat/src/index.ts b/stack--current/5-incubator/active/view--chat/src/index.ts index 0ec6093c..a1823fe6 100644 --- a/stack--current/5-incubator/active/view--chat/src/index.ts +++ b/stack--current/5-incubator/active/view--chat/src/index.ts @@ -1,160 +1,6 @@ -import is_promise from 'is-promise' -import { type Immutable } from '@offirmo-private/ts-types' - -import { LIB } from './consts.js' -import { type Step } from './types.js' -import { create_dummy_progress_promise } from './utils.js' - -///////////////////////////////////////////////// - - -function is_step_input(step: Immutable): boolean { - return step && step.type.startsWith('ask_') -} - -function create({ - DEBUG, - gen_next_step, - ui, - inter_msg_delay_ms = 0, - after_input_delay_ms = 0, - to_prettified_str = x => x, // work with browser -}) { - if (DEBUG) console.log('↘ create()') - - - async function ask_user(step) { - if (DEBUG) console.log('↘ ask_user(\n', to_prettified_str(step, {outline: true}), '\n)') - - let answer = '' - const ok = true // TODO used for confirmation - do { - await ui.display_message({msg: step.msg_main, choices: step.choices}) - answer = await ui.read_answer(step) - if (DEBUG) console.log(`↖ ask_user(…) answer = "${answer}"`) - } while (!ok) - await ui.pretend_to_think(after_input_delay_ms) - - let acknowledged = false - if (step.choices.length) { - const selected_choice = step.choices.find(choice => choice.value === answer) - if (selected_choice.msgg_acknowledge) { - await ui.display_message({msg: selected_choice.msgg_acknowledge(answer)}) - acknowledged = true - } - } - if (!acknowledged && step.msgg_acknowledge) { - await ui.display_message({msg: step.msgg_acknowledge(answer)}) - acknowledged = true - } - if (!acknowledged) { - // Fine! It's optional. - if (DEBUG) console.warn('You may want to add an acknowledge message to this step.') - } - - return answer - } - - async function execute_step(step) { - if (DEBUG) console.log('↘ execute_step(\n', to_prettified_str(step, {outline: true}), '\n)') - - switch (step.type) { - case 'simple_message': - await ui.pretend_to_think(inter_msg_delay_ms) - await ui.display_message({ msg: step.msg_main }) - break - - case 'progress': - await ui.display_progress({ - progress_promise: step.progress_promise - || create_dummy_progress_promise({ DURATION_MS: step.duration_ms }), - msg: step.msg_main, - msgg_acknowledge: step.msgg_acknowledge, - }) - .then(() => true, () => false) - .then(success => { - if (step.callback) - step.callback(success) - }) - break - - case 'ask_for_confirmation': - case 'ask_for_string': - case 'ask_for_choice': { - await ui.pretend_to_think(inter_msg_delay_ms) - const answer = await ask_user(step) - - let reported = false - if (step.choices.length) { - const selected_choice = step.choices.find(choice => choice.value === answer) - if (selected_choice.callback) { - await selected_choice.callback(answer) - reported = true - } - } - if (!reported && step.callback) { - await step.callback(answer) - reported = true - } - if (!reported) { - const err = new Error('CNF reporting callback in ask for result!') - err.step = step - throw err - } - return answer - } - default: - throw new Error(`Unsupported step type: "${step.type}"!`) - } - } - - async function start() { - if (DEBUG) console.log('↘ start()') - try { - await ui.setup() - let should_exit = false - let last_step = undefined // just in case - let last_answer = undefined // just in case - do { - const step_start_timestamp_ms = +new Date() - const yielded_step = gen_next_step.next({last_step, last_answer}) - - // just in case the returned step is a promise. - const {value: raw_step, done} = is_promise(yielded_step) - ? await ui.spin_until_resolution(yielded_step) - : yielded_step - - if (done) { - should_exit = true - continue - } - - const step = normalize_step(raw_step) - const elapsed_time_ms = (+new Date()) - step_start_timestamp_ms - if (is_step_input(last_step)) { - // pretend to have processed the user answer - await ui.pretend_to_think(Math.max(0, after_input_delay_ms - elapsed_time_ms)) - } - - last_answer = await execute_step(step) - last_step = step - } while (!should_exit) - await ui.teardown() - } - catch (e) { - await ui.teardown() - throw e - } - } - - return { - start, - } -} - -///////////////////////////////////////////////// +import { PProgress as PromiseWithProgress } from 'p-progress' export { + // for convenience PromiseWithProgress, - create, } diff --git a/stack--current/5-incubator/active/view--chat/src/loop/demo.ts b/stack--current/5-incubator/active/view--chat/src/loop/demo.ts new file mode 100755 index 00000000..c24c4918 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/loop/demo.ts @@ -0,0 +1,12 @@ +import generator_func from '../__fixtures/tour.js' +import primitives from '../__fixtures/primitives--console.js' + +import { create } from './index.js' + +const chat = create({ + DEBUG: true, + gen_next_step: generator_func() as any, + primitives, +}) + +await chat.start() diff --git a/stack--current/5-incubator/active/view--chat/src/loop/index.ts b/stack--current/5-incubator/active/view--chat/src/loop/index.ts new file mode 100644 index 00000000..e224af50 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/loop/index.ts @@ -0,0 +1,176 @@ +import is_promise from 'is-promise' +import { type Immutable } from '@offirmo-private/ts-types' + +import { type ChatPrimitives } from '../implementation/types.js' +import { type Step } from '../types/index.js' +import { create_dummy_progress_promise } from '../utils/index.js' +import { StepsGenerator } from './types.js' + +///////////////////////////////////////////////// + +interface Options { + DEBUG: boolean + gen_next_step: StepsGenerator + primitives: ChatPrimitives + //inter_msg_delay_ms: number + //after_input_delay_ms: number + //to_prettified_str: (x: any) => string +} + +function create({ + DEBUG, + gen_next_step, + primitives, + //inter_msg_delay_ms = 0, + //after_input_delay_ms = 0, + //to_prettified_str = x => x, // work with browser +}: Options) { + if (DEBUG) console.log('↘ chat_loop.create()') + + async function start() { + if (DEBUG) console.log('↘ chat_loop.start()') + + try { + await primitives.setup() + + let should_exit = false + let last_step = undefined // just in case + let last_answer = undefined // just in case + do { + const step_start_timestamp_ms = +new Date() + const raw_yielded_step = gen_next_step.next({last_step, last_answer}) + console.log('raw_yielded_step', raw_yielded_step) + + // TODO can be a promise? + const yielded_step: any = is_promise(raw_yielded_step) + ? await primitives.spin_until_resolution(raw_yielded_step as any) + : raw_yielded_step + console.log('yielded_step', yielded_step) + + const { value: raw_step, done } = yielded_step + if (done) { + should_exit = true + continue + } + + console.log('about to execute step', raw_step) + throw new Error(`NIMP!`) + + /* + const step = normalize_step(raw_step) + const elapsed_time_ms = (+new Date()) - step_start_timestamp_ms + if (is_step_input(last_step)) { + // pretend to have processed the user answer + await primitives.pretend_to_think(Math.max(0, after_input_delay_ms - elapsed_time_ms)) + } + + last_answer = await execute_step(step) + last_step = step + */ + } while (!should_exit) + } + catch (err) { + if (DEBUG) console.error('chat_loop encountered error:', err) + throw err + } + finally { + await primitives.teardown() + } + } +/* + async function ask_user(step) { + if (DEBUG) console.log('↘ ask_user(\n', to_prettified_str(step, {outline: true}), '\n)') + + let answer = '' + const ok = true // TODO used for confirmation + do { + await primitives.display_message({msg: step.msg_main, choices: step.choices}) + answer = await primitives.read_answer(step) + if (DEBUG) console.log(`↖ ask_user(…) answer = "${answer}"`) + } while (!ok) + await primitives.pretend_to_think(after_input_delay_ms) + + let acknowledged = false + if (step.choices.length) { + const selected_choice = step.choices.find(choice => choice.value === answer) + if (selected_choice.msgg_acknowledge) { + await primitives.display_message({msg: selected_choice.msgg_acknowledge(answer)}) + acknowledged = true + } + } + if (!acknowledged && step.msgg_acknowledge) { + await primitives.display_message({msg: step.msgg_acknowledge(answer)}) + acknowledged = true + } + if (!acknowledged) { + // Fine! It's optional. + if (DEBUG) console.warn('You may want to add an acknowledge message to this step.') + } + + return answer + } + + async function execute_step(step) { + if (DEBUG) console.log('↘ execute_step(\n', to_prettified_str(step, {outline: true}), '\n)') + + switch (step.type) { + case 'simple_message': + await primitives.pretend_to_think(inter_msg_delay_ms) + await primitives.display_message({ msg: step.msg_main }) + break + + case 'progress': + await primitives.display_progress({ + progress_promise: step.progress_promise + || create_dummy_progress_promise({ DURATION_MS: step.duration_ms }), + msg: step.msg_main, + msgg_acknowledge: step.msgg_acknowledge, + }) + .then(() => true, () => false) + .then(success => { + if (step.callback) + step.callback(success) + }) + break + + case 'ask_for_confirmation': + case 'ask_for_string': + case 'ask_for_choice': { + await primitives.pretend_to_think(inter_msg_delay_ms) + const answer = await ask_user(step) + + let reported = false + if (step.choices.length) { + const selected_choice = step.choices.find(choice => choice.value === answer) + if (selected_choice.callback) { + await selected_choice.callback(answer) + reported = true + } + } + if (!reported && step.callback) { + await step.callback(answer) + reported = true + } + if (!reported) { + const err = new Error('CNF reporting callback in ask for result!') + err.step = step + throw err + } + return answer + } + default: + throw new Error(`Unsupported step type: "${step.type}"!`) + } + } + + */ + return { + start, + } +} + +///////////////////////////////////////////////// + +export { + create, +} diff --git a/stack--current/5-incubator/active/view--chat/src/loop/types.ts b/stack--current/5-incubator/active/view--chat/src/loop/types.ts new file mode 100644 index 00000000..6d51ae4a --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/loop/types.ts @@ -0,0 +1,26 @@ + +import { type Step } from '../types/index.js' + +///////////////////////////////////////////////// + +// what is yielded by the generator +type StepsGeneratorYield = Step + +// what is eventually returned by the generator +type StepsGeneratorReturn = Step | Promise> + +// parameters of the generator.next() method +type StepsGeneratorNext = unknown + +// all together +type StepsGenerator = Generator< + StepsGeneratorYield, + StepsGeneratorReturn, + StepsGeneratorNext +> + +///////////////////////////////////////////////// + +export { + type StepsGenerator, +} diff --git a/stack--current/5-incubator/active/view--chat/src/types.ts b/stack--current/5-incubator/active/view--chat/src/types.ts deleted file mode 100644 index 2a2dbce5..00000000 --- a/stack--current/5-incubator/active/view--chat/src/types.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { PProgress as PromiseWithProgress } from 'p-progress' - -import { LIB } from './consts' - -///////////////////////////////////////////////// - - -interface BaseStep { - -} - -interface SimpleMessageStep extends BaseStep { - type: 'simple_message' - - msg: string -} - -interface PerceivedLaborStep extends BaseStep { - type: 'perceived_labor' - - msg_before?: string - duration_ms?: number - msg_after?: string -} - -interface AskForConfirmationStep extends BaseStep { - type: 'ask_for_confirmation' - - prompt?: string - msg_after?: (confirmation: boolean) => string - -} - -interface TaskProgressStep extends BaseStep { - type: 'progress' - - msg_before?: string - task_promise: Promise | PromiseWithProgress - msg_after?: (success: boolean, result: T | Error) => string -} - -type Step = - | SimpleMessageStep - | PerceivedLaborStep - | AskForConfirmationStep - | TaskProgressStep - - - -/* - type: - | 'ask_for_choice' - - msg_main: string - msg_details?: string - choices?: { - value: string - label: string - }[] - validator?: (value: string) => boolean - - -} - */ - - -///////////////////////////////////////////////// - -function normalize_step(step: Step) { - try { - if (step.type === 'ask_for_confirmation' && step !== STEP_CONFIRM) - step = Object.assign( - {}, - STEP_CONFIRM, - step, - ) - - if (!step.msg_main) - throw new Error(`${LIB}: Step is missing main message!`) - - if (!step.type) { - if (!step.choices) - throw new Error(`${LIB}: Step type is unknown and not inferrable!`) - - step.type = 'ask_for_choice' - } - - step = Object.assign( - { - validator: null, - choices: [], - }, - step, - ) - - step.choices = step.choices.map(normalize_choice) - - if (step.choices.length) { - const known_values = new Set() - step.choices.forEach((choice, index) => { - if (known_values.has(choice.value)) { - const err = new Error(`${LIB}: colliding choices with the same value!`) - err.details = { - choice, - value: choice.value, - index, - } - throw err - } - known_values.add(choice.value) - }) - } - - - return step - } - catch (e) { - console.error(to_prettified_str(step)) - throw e - } -} - -function normalize_choice(choice) { - // TODO auto-id - try { - if (!choice.hasOwnProperty('value') || typeof choice.value === 'undefined') - throw new Error('Choice has no value!') - choice.msg_cta = choice.msg_cta || String(choice.value) - return choice - } - catch (e) { - console.error(to_prettified_str(choice)) - throw e - } -} - -///////////////////////////////////////////////// - -export { - Step, -} diff --git a/stack--current/5-incubator/active/view--chat/src/types/index.ts b/stack--current/5-incubator/active/view--chat/src/types/index.ts new file mode 100644 index 00000000..fdc63323 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/types/index.ts @@ -0,0 +1 @@ +export * from './types.js' diff --git a/stack--current/5-incubator/active/view--chat/src/types/type-guards.ts.txt b/stack--current/5-incubator/active/view--chat/src/types/type-guards.ts.txt new file mode 100644 index 00000000..5f42a98c --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/types/type-guards.ts.txt @@ -0,0 +1,79 @@ +import { Step, StepType } from './types' + + +function isꓽinput_step(step: Step): step is AskForConfirmationStep | InputStep { + switch(step?.type) { + case StepType.ask_for_confirmation: + case StepType.input: + return true + default: + return false + } +} + +function normalizeꓽstep(step: Step): Required> { + throw new Error(`NIMP!`) + /* + try { + if (step.type === 'ask_for_confirmation' && step !== STEP_CONFIRM) + step = Object.assign( + {}, + STEP_CONFIRM, + step, + ) + + if (!step.msg_main) + throw new Error(`${LIB}: Step is missing main message!`) + + if (!step.type) { + if (!step.choices) + throw new Error(`${LIB}: Step type is unknown and not inferrable!`) + + step.type = 'ask_for_choice' + } + + step = Object.assign( + { + validator: null, + choices: [], + }, + step, + ) + + step.choices = step.choices.map(normalize_choice) + + if (step.choices.length) { + const known_values = new Set() + step.choices.forEach((choice, index) => { + if (known_values.has(choice.value)) { + const err = new Error(`${LIB}: colliding choices with the same value!`) + err.details = { + choice, + value: choice.value, + index, + } + throw err + } + known_values.add(choice.value) + }) + } + + + return step + */ +} + +/* +function normalize_choice(choice) { + // TODO auto-id + try { + if (!choice.hasOwnProperty('value') || typeof choice.value === 'undefined') + throw new Error('Choice has no value!') + choice.msg_cta = choice.msg_cta || String(choice.value) + return choice + } + catch (e) { + console.error(to_prettified_str(choice)) + throw e + } +}*/ diff --git a/stack--current/5-incubator/active/view--chat/src/types/types.ts b/stack--current/5-incubator/active/view--chat/src/types/types.ts new file mode 100644 index 00000000..1a9fc8d0 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/src/types/types.ts @@ -0,0 +1,89 @@ +import { Enum } from 'typescript-string-enums' +import { PProgress as PromiseWithProgress } from 'p-progress' +import { type Immutable } from '@offirmo-private/ts-types' + +///////////////////////////////////////////////// + +export const StepType = Enum( + 'simple_message', + 'perceived_labor', + 'ask_for_confirmation', + 'progress', + 'input', +) +export type StepType = Enum // eslint-disable-line no-redeclare + +///////////////////////////////////////////////// + +// TODO more async?? everything? + +interface BaseStep { + // TODO needed? +} + +interface SimpleMessageStep extends BaseStep { + type: typeof StepType.simple_message + + msg: ContentType +} + +interface PerceivedLaborStep extends BaseStep { + type: typeof StepType.perceived_labor + + msg_before?: ContentType + duration_ms?: number + msg_after?: ContentType + + // callback? +} + +interface TaskProgressStep extends BaseStep { + type: typeof StepType.progress + + msg_before?: ContentType + task_promise: Promise | PromiseWithProgress + msg_after?: (success: boolean, result: T | Error) => ContentType + + callback: (success: boolean, result: T | Error) => void +} + +// TODO merge with input? +interface AskForConfirmationStep extends BaseStep { + type: typeof StepType.ask_for_confirmation + + prompt?: string + msg_after?: (confirmation: boolean) => ContentType + + callback: (confirmation: boolean) => void +} + +// TODO refine +// TODO select between choices +// TODO types +interface InputStep extends BaseStep { + type: typeof StepType.input + + prompt: ContentType + normalizer?: (raw: T) => T + msg_as_user: (value: T) => ContentType + validators: Array<(value: T) => [ boolean, ContentType ]> + msg_acknowledge: (value: T) => ContentType + + callback: (value: T) => void +} + +type Step = + | SimpleMessageStep + | PerceivedLaborStep + | TaskProgressStep + | AskForConfirmationStep + | InputStep + +///////////////////////////////////////////////// + +export { + type Step, + + // for convenience + PromiseWithProgress, +} diff --git a/stack--current/5-incubator/active/view--chat/src/utils.ts b/stack--current/5-incubator/active/view--chat/src/utils/index.ts similarity index 93% rename from stack--current/5-incubator/active/view--chat/src/utils.ts rename to stack--current/5-incubator/active/view--chat/src/utils/index.ts index 3df84eaa..b5d72757 100644 --- a/stack--current/5-incubator/active/view--chat/src/utils.ts +++ b/stack--current/5-incubator/active/view--chat/src/utils/index.ts @@ -8,14 +8,13 @@ function create_dummy_progress_promise({DURATION_MS = 2000, PERIOD_MS = 100} = { return new PromiseWithProgress((resolve, reject, progress) => { let count = 0 - let pulse = setInterval(() => { + const pulse = setInterval(() => { count++ const completion_rate = 1. * (count * PERIOD_MS) / DURATION_MS progress(completion_rate) if (completion_rate >= 1) { clearInterval(pulse) - pulse = null resolve() } }, PERIOD_MS) diff --git a/stack--current/5-incubator/active/view--chat/tosort/2019/types.ts b/stack--current/5-incubator/active/view--chat/tosort/2019/types.ts index 072528ab..6209ab2b 100644 --- a/stack--current/5-incubator/active/view--chat/tosort/2019/types.ts +++ b/stack--current/5-incubator/active/view--chat/tosort/2019/types.ts @@ -1,10 +1,3 @@ -///////////////////// -import { Enum } from 'typescript-string-enums' - -/////// - - -///////////////////// type ActionCallback = () => Promise diff --git a/stack--current/5-incubator/active/view--chat/tsconfig.json b/stack--current/5-incubator/active/view--chat/tsconfig.json new file mode 100755 index 00000000..f7b7a881 --- /dev/null +++ b/stack--current/5-incubator/active/view--chat/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../0-meta/tsconfig.json", + "include": [ + "../../../0-meta/typescript-custom-typings/*.d.ts", + "src/**/*.ts" + ] +} diff --git a/stack--current/package.json b/stack--current/package.json index 100d388a..9c6c3bdc 100644 --- a/stack--current/package.json +++ b/stack--current/package.json @@ -103,7 +103,6 @@ "bowser": "^2", "boxen": "^8", "browserslist": "^4", - "caught": "^0.1", "chai": "^5", "chai-as-promised": "^8", "chai-fetch-mock": "^3", diff --git a/stack--current/yarn.lock b/stack--current/yarn.lock index 4233b80e..84974b84 100644 --- a/stack--current/yarn.lock +++ b/stack--current/yarn.lock @@ -1904,11 +1904,6 @@ caniuse-lite@^1.0.30001663: resolved "https://packages.atlassian.com/api/npm/npm-remote/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz#d588d75c9682d3301956b05a3749652a80677df4" integrity sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g== -caught@^0.1: - version "0.1.3" - resolved "https://packages.atlassian.com/api/npm/npm-remote/caught/-/caught-0.1.3.tgz#f63db0d65f1bacea7cb4852cd8f1d72166a2c8bf" - integrity sha512-DTWI84qfoqHEV5jHRpsKNnEisVCeuBDscXXaXyRLXC+4RD6rFftUNuTElcQ7LeO7w622pfzWkA1f6xu5qEAidw== - chai-as-promised@^8: version "8.0.0" resolved "https://packages.atlassian.com/api/npm/npm-remote/chai-as-promised/-/chai-as-promised-8.0.0.tgz#7eda823f2a6fe9fd3a76bc76878886e955232e6f"