@curless/hotel-storefront 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/about.d.ts +24 -0
  2. package/dist/about.d.ts.map +1 -0
  3. package/dist/about.js +28 -0
  4. package/dist/about.js.map +1 -0
  5. package/dist/aggregate/make.js +2 -2
  6. package/dist/aggregate/make.js.map +1 -1
  7. package/dist/backend.d.ts +6 -0
  8. package/dist/backend.d.ts.map +1 -1
  9. package/dist/backend.js.map +1 -1
  10. package/dist/card.generated.d.ts +1 -1
  11. package/dist/card.generated.js +2 -2
  12. package/dist/card.generated.js.map +1 -1
  13. package/dist/coupon-words.d.ts +12 -0
  14. package/dist/coupon-words.d.ts.map +1 -0
  15. package/dist/coupon-words.js +19 -0
  16. package/dist/coupon-words.js.map +1 -0
  17. package/dist/dates.js +1 -1
  18. package/dist/dates.js.map +1 -1
  19. package/dist/demo-hotels.d.ts +6 -0
  20. package/dist/demo-hotels.d.ts.map +1 -1
  21. package/dist/demo-hotels.js +39 -15
  22. package/dist/demo-hotels.js.map +1 -1
  23. package/dist/huazhu/backend.d.ts +6 -0
  24. package/dist/huazhu/backend.d.ts.map +1 -1
  25. package/dist/huazhu/backend.js +38 -30
  26. package/dist/huazhu/backend.js.map +1 -1
  27. package/dist/huazhu/catalog.d.ts.map +1 -1
  28. package/dist/huazhu/catalog.js +27 -6
  29. package/dist/huazhu/catalog.js.map +1 -1
  30. package/dist/huazhu/client.js +1 -1
  31. package/dist/huazhu/client.js.map +1 -1
  32. package/dist/huazhu/make.d.ts.map +1 -1
  33. package/dist/huazhu/make.js +1 -0
  34. package/dist/huazhu/make.js.map +1 -1
  35. package/dist/huazhu/sandbox.d.ts.map +1 -1
  36. package/dist/huazhu/sandbox.js +5 -3
  37. package/dist/huazhu/sandbox.js.map +1 -1
  38. package/dist/paid.d.ts.map +1 -1
  39. package/dist/paid.js +3 -1
  40. package/dist/paid.js.map +1 -1
  41. package/dist/reference.d.ts.map +1 -1
  42. package/dist/reference.js +13 -6
  43. package/dist/reference.js.map +1 -1
  44. package/dist/server.d.ts +2 -2
  45. package/dist/server.js +4 -4
  46. package/dist/server.js.map +1 -1
  47. package/dist/supplier.d.ts +6 -0
  48. package/dist/supplier.d.ts.map +1 -1
  49. package/dist/supplier.js +1 -0
  50. package/dist/supplier.js.map +1 -1
  51. package/dist/surface.d.ts.map +1 -1
  52. package/dist/surface.js +27 -18
  53. package/dist/surface.js.map +1 -1
  54. package/dist/tools.d.ts +11 -0
  55. package/dist/tools.d.ts.map +1 -1
  56. package/dist/tools.js +98 -39
  57. package/dist/tools.js.map +1 -1
  58. package/dist/types.d.ts +23 -0
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/vocab.d.ts +15 -1
  61. package/dist/vocab.d.ts.map +1 -1
  62. package/dist/vocab.js +26 -2
  63. package/dist/vocab.js.map +1 -1
  64. package/package.json +1 -1
package/dist/tools.d.ts CHANGED
@@ -1148,6 +1148,17 @@ export declare const NOTE_PASSED = "A note from the guest was passed to the hote
1148
1148
  * the one who knows - and says it when the guest wants one.
1149
1149
  */
1150
1150
  export declare const notSoldHereWords: (group: string) => string;
1151
+ /**
1152
+ * A hotel none of whose rooms are on sale here (`none-on-sale`), said on its page
1153
+ * with the way out (2026-11-03 review, T5). The page's text had one sentence -
1154
+ * "This hotel has no rooms on sale here." - and the card said where to book it
1155
+ * instead, so a client that draws no card left the guest with a dead end. The
1156
+ * clause is the list's (see `NOT_SOLD_OUT`), so the list and the page say one
1157
+ * thing; the group is the deployment's own, or the hotel's in a market, never a
1158
+ * name written into the sentence. In the text and, word for word, in the
1159
+ * structure (`noRooms`): the model reads the structure.
1160
+ */
1161
+ export declare const notOnSaleWords: (group: string, why: string) => string;
1151
1162
  /**
1152
1163
  * From how many cities a country is a list of cities to choose from rather
1153
1164
  * than a list of hotels. China is forty; Malaysia's six hotels in three cities
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { aboutLines } from './about.js';
1
2
  import { DEFAULT_PAGE, FULL_CAPABILITIES, LASTING_REQUESTS, MAX_PAGE, REQUEST_KINDS, REQUEST_NOTE_MAX, guestTooLong, notPricedForNoAnswer, unansweredBy, unitWords, } from './backend.js';
3
+ import { usedOnWords } from './coupon-words.js';
2
4
  import { MAX_GUESTS, addDays, isDay, nights as nightsBetween } from './dates.js';
3
5
  import { FACILITIES } from './facilities.js';
4
6
  import { DESCRIPTION_MAX, POLICY_MAX, hotelName, hotelText } from './fence.js';
@@ -302,8 +304,8 @@ const forCapabilities = (tools, n, v, caps) => {
302
304
  ? 'A rate marked paid in advance is booked on its own, and not at once: the answer gives a card page in test mode and the time it is paid by, and the stay is booked once it is paid - no card is collected here. Every other rate is paid by nobody.'
303
305
  : 'No payment is taken and no card is collected.'} All of the trip is booked or none of it. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests.`
304
306
  : v.sandbox
305
- ? `Makes a booking in a simulated copy of ${v.brand}'s booking system, for testing: each stay gets its own confirmation code there, and none of it is a ${v.brand} reservation or holds a real room. ${caps.prepaid
306
- ? 'A rate marked paid in advance is booked on its own, and not at once: the answer gives a card page in test mode and the time it is paid by, and the stay is booked once it is paid - no card is collected here. Every other rate is paid by nobody.'
307
+ ? `Makes a simulated booking in a simulated copy of ${v.brand}'s booking system: each stay gets its own confirmation code there, and none of it is a ${v.brand} reservation or holds a real room. ${caps.prepaid
308
+ ? 'A rate marked paid in advance is booked on its own, and not at once: the answer gives a card page where no real money moves and the time it is paid by, and the stay is booked once it is paid - no card is collected here. Every other rate is paid by nobody.'
307
309
  : 'No payment is taken and no card is collected.'} All of the trip is booked or none of it. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests.`
308
310
  : `Books the trip's rooms with ${v.brand} for a named guest: each stay becomes the hotel's own reservation, with its own confirmation code, paid at the hotel. No payment is taken here and no card is collected. All of the trip is booked or none of it. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests the desk tries to meet.`,
309
311
  };
@@ -592,9 +594,10 @@ const allTools = (n, v) => {
592
594
  // could take the code to the real hotel.
593
595
  description: v.market
594
596
  ? // ⚠️ THE GROUP HOLDS THE BOOKING, NOT THE MARKET - and what a booking
595
- // makes is the group's: a hold, a test booking, or a reservation paid
596
- // at the hotel. The answer says which, stay by stay.
597
- `Books the trip's rooms for a named guest with the hotel group each stay belongs to, one group at a time: a trip holding two groups' hotels is booked group by group. What a booking makes is that group's - a demonstration hold that lapses on its own, a test booking in a simulated system, or the hotel's own reservation paid at the hotel - and the answer says which for every stay. No payment is taken here and no card is collected. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests.`
597
+ // makes is the group's: a hold, a booking that holds no room, or a
598
+ // reservation paid at the hotel. The answer says which, stay by stay.
599
+ // ⚠️ NO "DEMONSTRATION" / "TEST" / "SIMULATED" IN A MARKET (robin, 2026-11-03).
600
+ `Books the trip's rooms for a named guest with the hotel group each stay belongs to, one group at a time: a trip holding two groups' hotels is booked group by group. What a booking makes is that group's - a hold that lapses on its own and is not sent to the group, a booking that holds no room for anyone, or the hotel's own reservation paid at the hotel - and the answer says which for every stay. No payment is taken here and no card is collected. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests.`
598
601
  : v.demo
599
602
  ? `Makes a demonstration booking request for the trip's rooms, for a named guest: it returns a confirmation code and the moment the demonstration hold lapses on its own. ${v.brand} does not receive it and no real room is held. No payment is taken and no card is collected. Preferences - a bed type, a floor, a late arrival - ride along on the request.`
600
603
  : `Asks ${v.brand} to hold the trip's rooms for a named guest and returns a confirmation code and the moment the hold lapses. No payment is taken and no card is collected; the rooms are held, not paid for. Preferences - a bed type, a floor, a late arrival - ride along on the booking as requests the desk tries to meet.`,
@@ -1706,6 +1709,7 @@ const holderOf = (deps, supplier) => {
1706
1709
  demo: s.mode !== 'live',
1707
1710
  sandbox: s.mode === 'sandbox',
1708
1711
  test: s.mode === 'test',
1712
+ notReal: !!deps.vocab.market && s.mode !== 'live',
1709
1713
  };
1710
1714
  return {
1711
1715
  name: deps.vocab.brand,
@@ -1713,6 +1717,7 @@ const holderOf = (deps, supplier) => {
1713
1717
  demo: deps.vocab.demo,
1714
1718
  sandbox: !!deps.vocab.sandbox,
1715
1719
  test: !!deps.vocab.test,
1720
+ notReal: false,
1716
1721
  };
1717
1722
  };
1718
1723
  /**
@@ -1791,11 +1796,17 @@ const paidLine = (deps, b, fee) => {
1791
1796
  const back = formatMoney(p.refundAmount ?? p.amount);
1792
1797
  const whole = !p.refundAmount || p.refundAmount.minor === p.amount.minor;
1793
1798
  const unionpay = p.method === 'unionpay';
1794
- const test = h.sandbox
1795
- ? ` This is a test: ${unionpay ? "UnionPay's test system" : 'the card page in test mode'}, no real money moves, and the booking is in the simulated system.`
1796
- : h.test
1797
- ? ` This is a test: ${unionpay ? "UnionPay's test system" : 'the card page in test mode'}, no real money moves, and the booking is a test booking in ${h.name}'s test system.`
1798
- : '';
1799
+ // ⚠️ ONE WORD PER ENVIRONMENT (0.54.0, M9): simulated on the simulated copy, test on a
1800
+ // group's test system - and in a market neither, only the fact (`notReal`). A
1801
+ // simulation names no "test card" or "UnionPay's test system": that is the payment
1802
+ // channel's own environment, not the simulation's word.
1803
+ const test = h.notReal
1804
+ ? ` No real money moves, and this is not a real booking with ${h.name}.`
1805
+ : h.sandbox
1806
+ ? ' This is simulated: no real money moves, and the booking is a simulated booking.'
1807
+ : h.test
1808
+ ? ` This is a test: ${unionpay ? "UnionPay's test system" : 'the card page in test mode'}, no real money moves, and the booking is a test booking in ${h.name}'s test system.`
1809
+ : '';
1799
1810
  const ended = b.status === 'cancelled' || b.status === 'expired' || b.status === 'failed';
1800
1811
  const said = (() => {
1801
1812
  // Its money as it stands once the stay has ended - never "if it does not take it".
@@ -1826,7 +1837,7 @@ const paidLine = (deps, b, fee) => {
1826
1837
  case 'refunded':
1827
1838
  return `${whole ? `All ${back}` : `${back} of the ${amount} paid`} has gone back to the card it was paid with${p.refundReason ? ` - ${p.refundReason}` : ''}.`;
1828
1839
  case 'hotel-refunds':
1829
- return `The money is with ${h.name}, who charged the one-time card UnionPay issued: what its terms give back${fee && fee.minor > 0 ? ` after its fee of ${formatMoney(fee)}` : ''}, it returns to that card - not this connector${p.refundReason ? ` (${p.refundReason})` : ''}.`;
1840
+ return `The money is with ${h.name}, who charged the one-time card UnionPay issued: what its terms give back${fee && fee.minor > 0 ? ` after its fee of ${formatMoney(fee)}` : ''}, it returns to that card - nothing is refunded from here${p.refundReason ? ` (${p.refundReason})` : ''}.`;
1830
1841
  }
1831
1842
  }
1832
1843
  switch (p.status) {
@@ -1860,7 +1871,7 @@ const paidLine = (deps, b, fee) => {
1860
1871
  case 'refunded':
1861
1872
  return `${whole ? `All ${back}` : `${back} of the ${amount} paid`} has gone back to the card it was paid with${p.refundReason ? ` - ${p.refundReason}` : ''}.`;
1862
1873
  case 'hotel-refunds':
1863
- return `The money is with ${h.name}, who returns what its terms give back to the card it charged - not this connector.`;
1874
+ return `The money is with ${h.name}, who returns what its terms give back to the card it charged - nothing is refunded from here.`;
1864
1875
  }
1865
1876
  })();
1866
1877
  return `${said}${test}`;
@@ -1902,8 +1913,11 @@ const heldLine = (deps, b) => {
1902
1913
  const total = formatMoney(b.quote.total);
1903
1914
  const h = holderOf(deps, b.supplier);
1904
1915
  if (h.caps.booking === 'pay-at-hotel') {
1916
+ // ⚠️ A MARKET'S GROUP: the fact, in no environment's word (robin, 2026-11-03).
1917
+ if (h.notReal)
1918
+ return `${total} all in. **This booking was not sent to ${h.name}** - no room is held for anyone and nothing is paid, now or at any hotel.`;
1905
1919
  if (h.sandbox)
1906
- return `${total} in the simulated system. **This is a test booking** - not a ${h.name} reservation: no room is held and nothing is paid, now or at any hotel.`;
1920
+ return `${total} in the simulated system. **This is a simulated booking** - not a ${h.name} reservation: no room is held and nothing is paid, now or at any hotel.`;
1907
1921
  if (h.test)
1908
1922
  return `${total} in ${h.name}'s test system. **This is a test booking** - it holds no room for anyone, nothing is paid, now or at any hotel, and nobody needs to go there.`;
1909
1923
  return b.status === 'requested'
@@ -1922,14 +1936,14 @@ const heldLine = (deps, b) => {
1922
1936
  // nothing for a real stay; said as how long it lasts, and that it is only the
1923
1937
  // demonstration.
1924
1938
  if (h.demo)
1925
- return `${total} all in. **${demoBookingWords(h)}** Nothing has been charged and no card is needed. ${demoHoldWords(b, deps.trips.now())}`;
1939
+ return `${total} all in. **${demoBookingWords(h)}** Nothing has been charged and no card is needed. ${demoHoldWords(b, deps.trips.now(), h)}`;
1926
1940
  return `${total} all in. **Nothing has been charged** - the rooms are held until ${lapses}, and the guest presents a card at check-in.`;
1927
1941
  };
1928
1942
  /**
1929
1943
  * How long a demonstration hold lasts, as "in N minutes" from now - the hold
1930
1944
  * lapses by itself, and says nothing about the stay (T2, `heldLine`).
1931
1945
  */
1932
- const demoHoldWords = (b, now) => {
1946
+ const demoHoldWords = (b, now, h) => {
1933
1947
  const minutes = b.holdUntil ? Math.ceil((Date.parse(b.holdUntil) - now) / 60_000) : Number.NaN;
1934
1948
  if (Number.isNaN(minutes))
1935
1949
  return '';
@@ -1940,10 +1954,15 @@ const demoHoldWords = (b, now) => {
1940
1954
  : minutes < 48 * 60
1941
1955
  ? `in about ${Math.round(minutes / 60)} hours`
1942
1956
  : `in about ${Math.round(minutes / (24 * 60))} days`;
1957
+ // In a market, no "demonstration" (robin, 2026-11-03): only that it lapses and holds no room.
1958
+ if (h.notReal)
1959
+ return `It lapses by itself ${lasting} (no real room is held; the stay's own dates are not affected).`;
1943
1960
  return `This is a demonstration: it lapses by itself ${lasting} (demo only - no real room is held; the stay's own dates are not affected).`;
1944
1961
  };
1945
1962
  /** What a demonstration booking is, in one sentence - see `demoBookingNote`. */
1946
- const demoBookingWords = (h) => `This is a demonstration booking: ${h.name} has not received it and is not holding any room.`;
1963
+ const demoBookingWords = (h) => h.notReal
1964
+ ? `This booking was not sent to ${h.name}, and ${h.name} is not holding any room.`
1965
+ : `This is a demonstration booking: ${h.name} has not received it and is not holding any room.`;
1947
1966
  /**
1948
1967
  * Said on every answer about a demonstration booking - made, changed or
1949
1968
  * cancelled - and on nothing else: a hold of the demo's that a guest took for
@@ -1992,7 +2011,10 @@ const common = (deps, key) => {
1992
2011
  ? { market: { name: deps.vocab.brand, suppliers: deps.backend.suppliers() } }
1993
2012
  : {}),
1994
2013
  vocab: { noun: deps.vocab.noun, nounPlural: deps.vocab.nounPlural },
1995
- ...(deps.vocab.demo ? { demo: true, demoNote: noteFor(deps.vocab) } : { demo: false }),
2014
+ // A market's note is empty (`noteFor`): it labels no environment (robin, 2026-11-03).
2015
+ ...(deps.vocab.demo
2016
+ ? { demo: true, ...(noteFor(deps.vocab) ? { demoNote: noteFor(deps.vocab) } : {}) }
2017
+ : { demo: false }),
1996
2018
  ...(key ? memberBadge(deps, key) : {}),
1997
2019
  ...(langOf(deps, key) ? { lang: langOf(deps, key) } : {}),
1998
2020
  };
@@ -2118,6 +2140,11 @@ const quoteForCard = (quote, groupOf) => ({
2118
2140
  // The review's own order - public, member, coupon, total - each
2119
2141
  // figure the server's, so the card adds nothing up (10-30, ①).
2120
2142
  publicTotal: formatMoney(quote.member.publicTotal),
2143
+ // The room charges at the public rate, the taxes a row of their own -
2144
+ // the receipt every guest gets: room, taxes, less the tier, less the
2145
+ // coupon, is the total (0.54.0, M1: a member's started from the
2146
+ // public rate taxes in, a guest's from the room before them).
2147
+ publicSubtotal: formatMoney(money(quote.member.publicTotal.minor - (quote.taxesIncluded ? 0 : quote.taxesAndFees.minor), quote.total.currency)),
2121
2148
  memberTotal: formatMoney(money(quote.member.publicTotal.minor - quote.member.saved.minor, quote.total.currency)),
2122
2149
  },
2123
2150
  }
@@ -2158,7 +2185,7 @@ const couponTerms = (c, brand) => [
2158
2185
  c.state === 'expired'
2159
2186
  ? `ran out on ${c.validUntil} - cannot be used`
2160
2187
  : c.state === 'used'
2161
- ? 'already used on a booking'
2188
+ ? `already used ${usedOnWords(c.usedOn)}`
2162
2189
  : c.state === 'not-yet'
2163
2190
  ? `usable from ${c.validFrom} to ${c.validUntil}`
2164
2191
  : `until ${c.validUntil}`,
@@ -2515,6 +2542,8 @@ const labelWords = (r) => {
2515
2542
  ];
2516
2543
  return words.length ? ` [${words.join(', ')}]` : '';
2517
2544
  };
2545
+ /** What a list and a page both say of a hotel with nothing on sale here: other nights would not help. */
2546
+ const NOT_SOLD_OUT = 'not sold out, and other nights make no difference';
2518
2547
  /**
2519
2548
  * What is said of rates listed but not booked here (`Room.notSoldHere`) - in
2520
2549
  * the text and, word for word, in the structure (`notSoldHere`): the model reads
@@ -2522,7 +2551,18 @@ const labelWords = (r) => {
2522
2551
  * card says no more than 预付 on them (robin, 2026-10-30), so the assistant is
2523
2552
  * the one who knows - and says it when the guest wants one.
2524
2553
  */
2525
- export const notSoldHereWords = (group) => `Rates marked paid in advance are listed as ${group} sells them, but a stay paid in advance cannot be booked here yet: this connector books rates paid at the hotel. If the guest wants one, say so plainly - nothing is booked or charged - and offer a rate paid at the hotel, other dates, or booking it with ${group} directly.`;
2554
+ export const notSoldHereWords = (group) => `Rates marked paid in advance are listed as ${group} sells them, but a stay paid in advance cannot be booked here yet; only rates paid at the hotel can be. If the guest wants one, say so plainly - nothing is booked or charged - and offer a rate paid at the hotel, other dates, or booking it with ${group} directly.`;
2555
+ /**
2556
+ * A hotel none of whose rooms are on sale here (`none-on-sale`), said on its page
2557
+ * with the way out (2026-11-03 review, T5). The page's text had one sentence -
2558
+ * "This hotel has no rooms on sale here." - and the card said where to book it
2559
+ * instead, so a client that draws no card left the guest with a dead end. The
2560
+ * clause is the list's (see `NOT_SOLD_OUT`), so the list and the page say one
2561
+ * thing; the group is the deployment's own, or the hotel's in a market, never a
2562
+ * name written into the sentence. In the text and, word for word, in the
2563
+ * structure (`noRooms`): the model reads the structure.
2564
+ */
2565
+ export const notOnSaleWords = (group, why) => `${why.replace(/[.\s]+$/, '')} - ${NOT_SOLD_OUT}. To book it, the guest can use ${group}'s own channels: its app, its website or by phone.`;
2526
2566
  /** The group a hotel is with, by name - the deployment's own brand outside a market. */
2527
2567
  const groupNameOf = (deps, hotelId) => holderOf(deps, deps.backend.supplierOf?.(hotelId)).name;
2528
2568
  /** What a rate says about cancelling, as the text puts it. */
@@ -4087,7 +4127,7 @@ const putRoomLine = async (deps, t, ctx, key, store, args, detail) => {
4087
4127
  const prepaidNotHere = (room, open, stay, when) => {
4088
4128
  const group = groupNameOf(deps, hotelId);
4089
4129
  const said = [
4090
- `Nothing was booked or charged: ${room.name} is paid in advance, and a stay paid in advance cannot be booked here yet - this connector books rates paid at the hotel.`,
4130
+ `Nothing was booked or charged: ${room.name} is paid in advance, and a stay paid in advance cannot be booked here yet - only rates paid at the hotel can be.`,
4091
4131
  open.length
4092
4132
  ? `Paid at the hotel at ${detail.hotel.name} ${when}: ${open.map((r) => `${r.name} (\`${r.id}\`)`).join(', ')}.`
4093
4133
  : `No rate at ${detail.hotel.name} ${when} is paid at the hotel.`,
@@ -4764,7 +4804,15 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
4764
4804
  // backend contract, but "the next backend honours it" is not something the
4765
4805
  // card can be built on - and the cost of it being wrong is the whole group
4766
4806
  // in the model's context.
4767
- const hotels = answer.hotels.slice(0, MAX_PAGE);
4807
+ // ⚠️ A HOTEL NOT ON SALE HERE SAYS WHERE IT IS (review r2, T5): its page said
4808
+ // the group's own channels, its tile on a list - the answer a model often
4809
+ // stops at - only that nothing was on sale. In the text and the structure.
4810
+ const hotels = answer.hotels.slice(0, MAX_PAGE).map((h) => h.noRoomsForStayCode === 'none-on-sale' && h.noRoomsForStay
4811
+ ? {
4812
+ ...h,
4813
+ noRoomsForStay: `${h.noRoomsForStay.replace(/[.\s]+$/, '')} - it can be booked through ${groupNameOf(deps, h.id)}'s own channels: its app, its website or by phone.`,
4814
+ }
4815
+ : h);
4768
4816
  const offset = asked.offset ?? 0;
4769
4817
  const total = Math.max(answer.total, offset + hotels.length);
4770
4818
  const cities = [...new Set(hotels.map((h) => h.city))];
@@ -4936,7 +4984,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
4936
4984
  // assistant ask which city to list the rest in (2026-10-15 smoke run).
4937
4985
  // And not "tell me another city": the second run said there was one
4938
4986
  // hotel, then offered to look in another city (2026-10-15).
4939
- const inAll = `This connector has one ${branded(deps.vocab, deps.vocab.noun)} in all, ${sole.name}${sole.city ? ` in ${sole.city}` : ''}. There is no other hotel and no other city here - nothing else to search for or to offer.`;
4987
+ const inAll = `There is one ${branded(deps.vocab, deps.vocab.noun)} here in all, ${sole.name}${sole.city ? ` in ${sole.city}` : ''}. There is no other hotel and no other city here - nothing else to search for or to offer.`;
4940
4988
  return {
4941
4989
  ...page,
4942
4990
  // In both, in the same words: a host may hand its model this rather
@@ -5158,7 +5206,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
5158
5206
  : ''}${
5159
5207
  // The other kind, said as its own (U11): other nights will not help.
5160
5208
  hotels.some((h) => h.noRoomsForStayCode === 'none-on-sale')
5161
- ? `; ${hotels.filter((h) => h.noRoomsForStayCode === 'none-on-sale').length} in this answer ${hotels.filter((h) => h.noRoomsForStayCode === 'none-on-sale').length === 1 ? 'has' : 'have'} no rooms on sale through this connector - not sold out, and other nights make no difference`
5209
+ ? `; ${hotels.filter((h) => h.noRoomsForStayCode === 'none-on-sale').length} in this answer ${hotels.filter((h) => h.noRoomsForStayCode === 'none-on-sale').length === 1 ? 'has' : 'have'} no rooms on sale here - ${NOT_SOLD_OUT}`
5162
5210
  : ''}.`
5163
5211
  : '',
5164
5212
  ].filter(Boolean);
@@ -5172,7 +5220,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
5172
5220
  // only "2 hotels", the model asked for the list again to learn which.
5173
5221
  const degraded = degradedOf(deps, answer);
5174
5222
  const inAll = homeList
5175
- ? `This connector has ${total} ${branded(deps.vocab, deps.vocab.nounPlural)} in all, and this answer lists every one: ${hotels
5223
+ ? `There are ${total} ${branded(deps.vocab, deps.vocab.nounPlural)} here in all, and this answer lists every one: ${hotels
5176
5224
  .map((h) => `${hotelName(h.name)}${h.city ? ` in ${h.city}` : ''}`)
5177
5225
  .join('; ')}. There is no other ${deps.vocab.noun} and no other city here - nothing else to list, search for or offer.`
5178
5226
  : '';
@@ -5276,7 +5324,9 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
5276
5324
  };
5277
5325
  }
5278
5326
  const h = detail.hotel;
5279
- const description = hotelText('description', h.name, h.summary, DESCRIPTION_MAX, deps.vocab.demo);
5327
+ const description = hotelText('description', h.name,
5328
+ // Its 【…】 parts a line each, the words as they came (`aboutLines`).
5329
+ aboutLines(h.summary), DESCRIPTION_MAX, deps.vocab.demo);
5280
5330
  const policies = await deps.backend.policies(h.id);
5281
5331
  const extras = await deps.backend.extrasFor(h.id, stay);
5282
5332
  const base = {
@@ -5323,17 +5373,23 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
5323
5373
  : [];
5324
5374
  const flex = dateFlex(dates, stay);
5325
5375
  if (detail.noRooms || !detail.rooms) {
5376
+ // ⚠️ NOTHING ON SALE HERE SAYS WHERE TO BOOK IT (T5): the same sentence
5377
+ // in the text and in `noRooms`, which the card does not draw for this code
5378
+ // (it says it in the guest's language from `noRoomsCode`).
5379
+ const said = detail.noRooms && detail.noRoomsCode === 'none-on-sale'
5380
+ ? notOnSaleWords(groupNameOf(deps, h.id), detail.noRooms)
5381
+ : detail.noRooms;
5326
5382
  return {
5327
5383
  structured: {
5328
5384
  view: 'no-rooms',
5329
5385
  ...base,
5330
5386
  stay,
5331
- noRooms: detail.noRooms,
5387
+ noRooms: said,
5332
5388
  ...(detail.noRoomsCode ? { noRoomsCode: detail.noRoomsCode } : {}),
5333
5389
  ...(detail.noRoomsArgs ? { noRoomsArgs: detail.noRoomsArgs } : {}),
5334
5390
  ...flex.structured,
5335
5391
  },
5336
- text: [detail.noRooms ?? 'Nothing is available for those nights.', flex.text]
5392
+ text: [said ?? 'Nothing is available for those nights.', flex.text]
5337
5393
  .filter(Boolean)
5338
5394
  .join(' '),
5339
5395
  };
@@ -5501,7 +5557,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
5501
5557
  continue;
5502
5558
  const h = detail.hotel;
5503
5559
  read.push({ id: h.id, name: h.name });
5504
- const about = hotelText('description', h.name, [h.summary, h.amenities.length ? `Has: ${h.amenities.join(', ')}.` : '']
5560
+ const about = hotelText('description', h.name, [aboutLines(h.summary), h.amenities.length ? `Has: ${h.amenities.join(', ')}.` : '']
5505
5561
  .filter(Boolean)
5506
5562
  .join('\n'), DESCRIPTION_MAX, deps.vocab.demo);
5507
5563
  const extras = await deps.backend.extrasFor(h.id, stay);
@@ -5656,7 +5712,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
5656
5712
  text: extras.length
5657
5713
  ? extras
5658
5714
  .map((e) => `- ${e.name}${ctx.noCards ? ` (\`${e.id}\`)` : ''} — ${formatMoney(e.price)} ${unitWords(e.per)}${held[e.id] ? ` (${booked?.confirmationCode} holds ${held[e.id]})` : ''}`)
5659
- .concat(deps.vocab.demo ? [noteFor(deps.vocab)] : [])
5715
+ .concat(deps.vocab.demo && noteFor(deps.vocab) ? [noteFor(deps.vocab)] : [])
5660
5716
  .join('\n')
5661
5717
  : `${detail.hotel.name} has no add-ons listed here.`,
5662
5718
  };
@@ -6230,15 +6286,18 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
6230
6286
  cancelled.payment
6231
6287
  ? // Paid here in advance: what becomes of the money is the answer.
6232
6288
  `${cancelled.confirmationCode} is ${cancelled.status === 'expired' ? 'called off before it was paid' : 'cancelled'}. ${paidLine(deps, cancelled, charged)}`
6233
- : h.sandbox
6234
- ? // A test booking cancelled in the simulation: no hotel, no charge (2026-10-05 audit, P2).
6235
- `${cancelled.confirmationCode} is cancelled in the simulated system. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this one is a test, and nothing is charged.`}`
6236
- : h.test
6237
- ? `${cancelled.confirmationCode} is cancelled in ${h.name}'s test system. ${charged.minor === 0 ? 'No fee.' : `A live booking on this rate would be charged ${formatMoney(charged)}; this one is a test, and nothing is charged.`}`
6238
- : h.demo
6239
- ? // Nobody charges a demonstration anything (2026-10-05 audit, P2).
6240
- `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this is a demonstration, and nothing is charged.`}`
6241
- : `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `The hotel charges ${formatMoney(charged)}.`}`,
6289
+ : h.notReal
6290
+ ? // A market's group: the fact, in no environment's word (robin, 2026-11-03).
6291
+ `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this booking was never sent to ${h.name}, and nothing is charged.`}`
6292
+ : h.sandbox
6293
+ ? // A simulated booking cancelled in the simulation: no hotel, no charge (2026-10-05 audit, P2).
6294
+ `${cancelled.confirmationCode} is cancelled in the simulated system. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this one is simulated, and nothing is charged.`}`
6295
+ : h.test
6296
+ ? `${cancelled.confirmationCode} is cancelled in ${h.name}'s test system. ${charged.minor === 0 ? 'No fee.' : `A live booking on this rate would be charged ${formatMoney(charged)}; this one is a test, and nothing is charged.`}`
6297
+ : h.demo
6298
+ ? // Nobody charges a demonstration anything (2026-10-05 audit, P2).
6299
+ `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `A real booking on this rate would be charged ${formatMoney(charged)}; this is a demonstration, and nothing is charged.`}`
6300
+ : `${cancelled.confirmationCode} is cancelled. ${charged.minor === 0 ? 'No fee.' : `The hotel charges ${formatMoney(charged)}.`}`,
6242
6301
  couponNow?.said ?? '',
6243
6302
  demoBookingNote(deps, cancelled.supplier),
6244
6303
  deps.vocab.demo ? noteFor(deps.vocab) : '',
@@ -6488,7 +6547,7 @@ const answerTool = async (deps, name, rawArgs, ctx) => {
6488
6547
  // would find out at the desk.
6489
6548
  return {
6490
6549
  structured: { view: 'member-not-found', ...common(deps, key), query: number },
6491
- text: `No ${deps.vocab.brand} membership matches that number and surname. Check both as they are printed; nothing else is needed - this connector never asks for a password.`,
6550
+ text: `No ${deps.vocab.brand} membership matches that number and surname. Check both as they are printed; nothing else is needed - a password is never asked for here.`,
6492
6551
  };
6493
6552
  }
6494
6553
  deps.members.set(key, member);